Esercitazione: Aggiungere unit test per progetti con oggetti visivi di Power BITutorial: Add unit tests for Power BI visual projects

Questo articolo contiene le nozioni di base per la scrittura di unit test per gli oggetti visivi di Power BI, include le procedure per:This article describes the basics of writing unit tests for your Power BI visuals, including how to:

  • Configurare il framework di test dello strumento di esecuzione di test per JavaScript Karma, Jasmine.Set up the Karma JavaScript test runner testing framework, Jasmine.
  • Usare il pacchetto powerbi-visuals-utils-testutils.Use the powerbi-visuals-utils-testutils package.
  • Usare simulazioni per contribuire a semplificare gli unit test degli oggetti visivi di Power BI.Use mocks and fakes to help simplify unit testing of Power BI visuals.

PrerequisitiPrerequisites

  • Un progetto con oggetti visivi di Power BI installatoAn installed Power BI visuals project
  • Un ambiente Node.js configuratoA configured Node.js environment

Installare e configurare lo strumento di esecuzione di test per JavaScript Karma e JasmineInstall and configure the Karma JavaScript test runner and Jasmine

Aggiungere le librerie necessarie al file package.json nella sezione devDependencies:Add the required libraries to the package.json file in the devDependencies section:

"@babel/polyfill": "^7.2.5",
"@types/d3": "5.5.0",
"@types/jasmine": "2.5.37",
"@types/jasmine-jquery": "1.5.28",
"@types/jquery": "2.0.41",
"@types/karma": "3.0.0",
"@types/lodash-es": "4.17.1",
"coveralls": "3.0.2",
"istanbul-instrumenter-loader": "^3.0.1",
"jasmine": "2.5.2",
"jasmine-core": "2.5.2",
"jasmine-jquery": "2.1.1",
"jquery": "3.1.1",
"karma": "3.1.1",
"karma-chrome-launcher": "2.2.0",
"karma-coverage": "1.1.2",
"karma-coverage-istanbul-reporter": "^2.0.4",
"karma-jasmine": "2.0.1",
"karma-junit-reporter": "^1.2.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-typescript": "^3.0.13",
"karma-typescript-preprocessor": "0.4.0",
"karma-webpack": "3.0.5",
"puppeteer": "1.17.0",
"style-loader": "0.23.1",
"ts-loader": "5.3.0",
"ts-node": "7.0.1",
"tslint": "^5.12.0",
"webpack": "4.26.0"

Per altre informazioni su package.json, vedere la descrizione in npm-package.json.To learn more about package.json, see the description at npm-package.json.

Salvare il file package.json ed eseguire il comando seguente nella posizione package.json:Save the package.json file and, at the package.json location, run the following command:

npm install

Lo strumento di gestione pacchetti installa tutti i nuovi pacchetti aggiunti a package.json.The package manager installs all new packages that are added to package.json.

Per eseguire gli unit test, configurare lo strumento di esecuzione di test e la configurazione di webpack.To run unit tests, configure the test runner and webpack config.

Il codice seguente è un esempio del file test.webpack.config.js:The following code is a sample of the test.webpack.config.js file:

const path = require('path');
const webpack = require("webpack");

module.exports = {
    devtool: 'source-map',
    mode: 'development',
    optimization : {
        concatenateModules: false,
        minimize: false
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            },
            {
                test: /\.json$/,
                loader: 'json-loader'
            },
            {
                test: /\.tsx?$/i,
                enforce: 'post',
                include: /(src)/,
                exclude: /(node_modules|resources\/js\/vendor)/,
                loader: 'istanbul-instrumenter-loader',
                options: { esModules: true }
            },
            {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'less-loader',
                        options: {
                            paths: [path.resolve(__dirname, 'node_modules')]
                        }
                    }
                ]
            }
        ]
    },
    externals: {
        "powerbi-visuals-api": '{}'
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js', '.css']
    },
    output: {
        path: path.resolve(__dirname, ".tmp/test")
    },
    plugins: [
        new webpack.ProvidePlugin({
            'powerbi-visuals-api': null
        })
    ]
};

Il codice seguente è un esempio del file karma.conf.ts:The following code is a sample of the karma.conf.ts file:

"use strict";

const webpackConfig = require("./test.webpack.config.js");
const tsconfig = require("./test.tsconfig.json");
const path = require("path");

const testRecursivePath = "test/visualTest.ts";
const srcOriginalRecursivePath = "src/**/*.ts";
const coverageFolder = "coverage";

process.env.CHROME_BIN = require("puppeteer").executablePath();

import { Config, ConfigOptions } from "karma";

module.exports = (config: Config) => {
    config.set(<ConfigOptions>{
        mode: "development",
        browserNoActivityTimeout: 100000,
        browsers: ["ChromeHeadless"], // or Chrome to use locally installed Chrome browser
        colors: true,
        frameworks: ["jasmine"],
        reporters: [
            "progress",
            "junit",
            "coverage-istanbul"
        ],
        junitReporter: {
            outputDir: path.join(__dirname, coverageFolder),
            outputFile: "TESTS-report.xml",
            useBrowserName: false
        },
        singleRun: true,
        plugins: [
            "karma-coverage",
            "karma-typescript",
            "karma-webpack",
            "karma-jasmine",
            "karma-sourcemap-loader",
            "karma-chrome-launcher",
            "karma-junit-reporter",
            "karma-coverage-istanbul-reporter"
        ],
        files: [
            "node_modules/jquery/dist/jquery.min.js",
            "node_modules/jasmine-jquery/lib/jasmine-jquery.js",
            {
                pattern: './capabilities.json',
                watched: false,
                served: true,
                included: false
            },
            testRecursivePath,
            {
                pattern: srcOriginalRecursivePath,
                included: false,
                served: true
            }
        ],
        preprocessors: {
            [testRecursivePath]: ["webpack", "coverage"]
        },
        typescriptPreprocessor: {
            options: tsconfig.compilerOptions
        },
        coverageIstanbulReporter: {
            reports: ["html", "lcovonly", "text-summary", "cobertura"],
            dir: path.join(__dirname, coverageFolder),
            'report-config': {
                html: {
                    subdir: 'html-report'
                }
            },
            combineBrowserReports: true,
            fixWebpackSourcePaths: true,
            verbose: false
        },
        coverageReporter: {
            dir: path.join(__dirname, coverageFolder),
            reporters: [
                // reporters not supporting the `file` property
                { type: 'html', subdir: 'html-report' },
                { type: 'lcov', subdir: 'lcov' },
                // reporters supporting the `file` property, use `subdir` to directly
                // output them in the `dir` directory
                { type: 'cobertura', subdir: '.', file: 'cobertura-coverage.xml' },
                { type: 'lcovonly', subdir: '.', file: 'report-lcovonly.txt' },
                { type: 'text-summary', subdir: '.', file: 'text-summary.txt' },
            ]
        },
        mime: {
            "text/x-typescript": ["ts", "tsx"]
        },
        webpack: webpackConfig,
        webpackMiddleware: {
            stats: "errors-only"
        }
    });
};

Se necessario, è possibile modificare questa configurazione.If necessary, you can modify this configuration.

Il codice in karma.conf.js contiene la variabile seguente:The code in karma.conf.js contains the following variable:

  • recursivePathToTests: individua il codice del testrecursivePathToTests: Locates the test code

  • srcRecursivePath: individua il codice JavaScript di output dopo la compilazionesrcRecursivePath: Locates the output JavaScript code after compiling

  • srcCssRecursivePath: individua i CSS di output dopo la compilazione di un numero inferiore di file con stilisrcCssRecursivePath: Locates the output CSS after compiling less file with styles

  • srcOriginalRecursivePath: individua il codice sorgente dell'oggetto visivosrcOriginalRecursivePath: Locates the source code of your visual

  • coverageFolder: determina la posizione in cui deve essere creato il report di code coveragecoverageFolder: Determines where the coverage report is to be created

Il file di configurazione include le proprietà seguenti:The configuration file includes the following properties:

  • singleRun: true: i test vengono eseguiti in un sistema di integrazione continua oppure possono essere eseguiti una sola volta.singleRun: true: Tests are run on a continuous integration (CI) system, or they can be run one time. È possibile modificare l'impostazione in false per il debug dei test.You can change the setting to false for debugging your tests. Karma mantiene il browser in esecuzione in modo che sia possibile usare la console per il debug.Karma keeps the browser running so that you can use the console for debugging.

  • files: [...]: in questa matrice è possibile specificare i file da caricare nel browser.files: [...]: In this array, you can specify the files to load to the browser. In genere, si tratta di file di origine, test case e librerie (Jasmine, utilità di test).Usually, there are source files, test cases, libraries (Jasmine, test utilities). È possibile aggiungere altri file all'elenco, se necessario.You can add additional files to the list, as necessary.

  • preprocessors: in questa sezione vengono configurate le azioni che vengono eseguite prima degli unit test.preprocessors: In this section, you configure actions that run before the unit tests run. Precompilano il codice TypeScript in JavaScript, preparano i file di mappa di origine e generano il report della code coverage.They precompile the typescript to JavaScript, prepare source map files, and generate code coverage report. È possibile disabilitare coverage quando si esegue il debug dei test.You can disable coverage when you debug your tests. La code coverage genera codice aggiuntivo per il codice di controllo per la code coverage dei test, che complica i test di debug.Coverage generates additional code for check code for the test coverage, which complicates debugging tests.

Per le descrizioni di tutte le configurazioni di Karma, passare alla pagina Karma Configuration File (File di configurazione di Karma).For descriptions of all Karma configurations, go to the Karma Configuration File page.

Per praticità, è possibile aggiungere un comando test in scripts:For your convenience, you can add a test command into scripts:

{
    "scripts": {
        "pbiviz": "pbiviz",
        "start": "pbiviz start",
        "typings":"node node_modules/typings/dist/bin.js i",
        "lint": "tslint -r \"node_modules/tslint-microsoft-contrib\"  \"+(src|test)/**/*.ts\"",
        "pretest": "pbiviz package --resources --no-minify --no-pbiviz --no-plugin",
        "test": "karma start"
    }
    ...
}

È ora possibile iniziare la scrittura degli unit test.You're now ready to begin writing your unit tests.

Controllare l'elemento DOM dell'oggetto visivoCheck the DOM element of the visual

Per testare l'oggetto visivo, creare prima di tutto un'istanza dell'oggetto visivo stesso.To test the visual, first create an instance of visual.

Creare un generatore dell'istanza dell'oggetto visivoCreate a visual instance builder

Aggiungere un file visualBuilder.ts alla cartella test usando il codice seguente:Add a visualBuilder.ts file to the test folder by using the following code:

import {
    VisualBuilderBase
} from "powerbi-visuals-utils-testutils";

import {
    BarChart as VisualClass
} from "../src/visual";

import  powerbi from "powerbi-visuals-api";
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;

export class BarChartBuilder extends VisualBuilderBase<VisualClass> {
    constructor(width: number, height: number) {
        super(width, height);
    }

    protected build(options: VisualConstructorOptions) {
        return new VisualClass(options);
    }

    public get mainElement() {
        return this.element.children("svg.barChart");
    }
}

È disponibile un metodo build per la creazione di un'istanza dell'oggetto visivo.There's build method for creating an instance of your visual. mainElement è un metodo Get che restituisce un'istanza dell'elemento DOM (Document Object Model) "radice" nell'oggetto visivo.mainElement is a get method, which returns an instance of "root" document object model (DOM) element in your visual. Il metodo Get è facoltativo, ma semplifica la scrittura dello unit test.The getter is optional, but it makes writing the unit test easier.

A questo punto è disponibile una build di un'istanza dell'oggetto visivo.You now have a build of an instance of your visual. Verrà ora scritto il test case.Let's write the test case. Il test case controlla gli elementi SVG creati quando viene visualizzato l'oggetto visivo.The test case checks the SVG elements that are created when your visual is displayed.

Creare un file TypeScript per la scrittura di test caseCreate a typescript file to write test cases

Aggiungere un file visualTest.ts per i test case usando il codice seguente:Add a visualTest.ts file for the test cases by using the following code:

import powerbi from "powerbi-visuals-api";

import { BarChartBuilder } from "./VisualBuilder";

import {
    BarChart as VisualClass
} from "../src/visual";

import VisualBuilder = powerbi.extensibility.visual.test.BarChartBuilder;

describe("BarChart", () => {
    let visualBuilder: VisualBuilder;
    let dataView: DataView;

    beforeEach(() => {
        visualBuilder = new VisualBuilder(500, 500);
    });

    it("root DOM element is created", () => {
        expect(visualBuilder.mainElement).toBeInDOM();
    });
});

Vengono chiamati diversi metodi:Several methods are called:

  • describe: descrive un test case.describe: Describes a test case. Nell'ambito del framework Jasmine, spesso descrive una suite o un gruppo di specifiche.In the context of the Jasmine framework, it often describes a suite or group of specs.

  • beforeEach: viene chiamato prima di ogni chiamata del metodo it, definito nel metodo describe.beforeEach: Is called before each call of the it method, which is defined in the describe method.

  • it: definisce una singola specifica. Il metodo it deve contenere uno o più oggetti expectations.it: Defines a single spec. The it method should contain one or more expectations.

  • expect: crea una previsione per una specifica. Una specifica ha esito positivo se tutte le previsioni vengono passate senza errori.expect: Creates an expectation for a spec. A spec succeeds if all expectations pass without any failures.

  • toBeInDOM: uno dei metodi di matchers.toBeInDOM: One of the matchers methods. Per altre informazioni su matchers, vedere Jasmine Namespace: matchers (Spazio dei nomi di Jasmine: matchers).For more information about matchers, see Jasmine Namespace: matchers.

Per altre informazioni su Jasmine, vedere la pagina della documentazione del framework Jasmine.For more information about Jasmine, see the Jasmine framework documentation page.

Avviare gli unit testLaunch unit tests

Questo test verifica che venga creato l'elemento SVG radice degli oggetti visivi.This test checks that root SVG element of the visuals is created. Per eseguire lo unit test, immettere il comando seguente nello strumento da riga di comando:To run the unit test, enter the following command in the command-line tool:

npm run test

karma.js esegue il test case nel browser Chrome.karma.js runs the test case in the Chrome browser.

JavaScript Karma aperto in Chrome

Nota

È necessario installare Google Chrome in locale.You must install Google Chrome locally.

Nella finestra della riga di comando si ottiene l'output seguente:In the command-line window, you'll get following output:

> karma start

23 05 2017 12:24:26.842:WARN [watcher]: Pattern "E:/WORKSPACE/PowerBI/PowerBI-visuals-sampleBarChart/data/*.csv" does not match any file.
23 05 2017 12:24:30.836:WARN [karma]: No captured browser, open https://localhost:9876/
23 05 2017 12:24:30.849:INFO [karma]: Karma v1.3.0 server started at https://localhost:9876/
23 05 2017 12:24:30.850:INFO [launcher]: Launching browser Chrome with unlimited concurrency
23 05 2017 12:24:31.059:INFO [launcher]: Starting browser Chrome
23 05 2017 12:24:33.160:INFO [Chrome 58.0.3029 (Windows 10 0.0.0)]: Connected on socket /#2meR6hjXFmsE_fjiAAAA with id 5875251
Chrome 58.0.3029 (Windows 10 0.0.0): Executed 1 of 1 SUCCESS (0.194 secs / 0.011 secs)

=============================== Coverage summary ===============================
Statements   : 27.43% ( 65/237 )
Branches     : 19.84% ( 25/126 )
Functions    : 43.86% ( 25/57 )
Lines        : 20.85% ( 44/211 )
================================================================================

Come aggiungere dati statici per gli unit testHow to add static data for unit tests

Creare il file visualData.ts nella cartella test usando il codice seguente:Create the visualData.ts file in the test folder by using the following code:

import powerbi from "powerbi-visuals-api";
import DataView = powerbi.DataView;

import {
    testDataViewBuilder,
    getRandomNumbers
} from "powerbi-visuals-utils-testutils";

export class SampleBarChartDataBuilder extends TestDataViewBuilder {
    public static CategoryColumn: string = "category";
    public static MeasureColumn: string = "measure";

    public constructor() {
        super();
        ...
    }

    public getDataView(columnNames?: string[]): DataView {
        let dateView: any = this.createCategoricalDataViewBuilder([
            ...
        ],
        [
            ...
        ], columnNames).build();

        // there's client side computed maxValue
        let maxLocal = 0;
        this.valuesMeasure.forEach((item) => {
                if (item > maxLocal) {
                    maxLocal = item;
                }
        });
        (<any>dataView).categorical.values[0].maxLocal = maxLocal;
    }
}

La classe SampleBarChartDataBuilder estende TestDataViewBuilder e implementa il metodo astratto getDataView.The SampleBarChartDataBuilder class extends TestDataViewBuilder and implements the abstract method getDataView.

Quando si inseriscono i dati in bucket di campi dati, Power BI produce un oggetto dataview categorico basato sui dati.When you put data into data-field buckets, Power BI produces a categorical dataview object that's based on your data.

Bucket di campi dati

Negli unit test non sono disponibili funzioni di base di Power BI per riprodurre i dati.In unit tests, you don't have Power BI core functions to reproduce the data. Tuttavia, è necessario eseguire il mapping dei dati statici all'oggetto dataview categorico.But you need to map your static data to the categorical dataview. La classe TestDataViewBuilder consente di eseguirne il mapping.The TestDataViewBuilder class can help you map it.

Per altre informazioni sul mapping di viste dati, vedere DataViewMappings.For more information about Data View mapping, see DataViewMappings.

Nel metodo getDataView si chiama il metodo createCategoricalDataViewBuilder con i dati.In the getDataView method, you call the createCategoricalDataViewBuilder method with your data.

Il file capabilities.json per l'oggetto visivo sampleBarChart include gli oggetti dataRoles and dataViewMapping:In sampleBarChart visual capabilities.json file, we have dataRoles and dataViewMapping objects:

"dataRoles": [
    {
        "displayName": "Category Data",
        "name": "category",
        "kind": "Grouping"
    },
    {
        "displayName": "Measure Data",
        "name": "measure",
        "kind": "Measure"
    }
],
"dataViewMappings": [
    {
        "conditions": [
            {
                "category": {
                    "max": 1
                },
                "measure": {
                    "max": 1
                }
            }
        ],
        "categorical": {
            "categories": {
                "for": {
                    "in": "category"
                }
            },
            "values": {
                "select": [
                    {
                        "bind": {
                            "to": "measure"
                        }
                    }
                ]
            }
        }
    }
],

Per generare lo stesso mapping, è necessario impostare i parametri seguenti sul metodo createCategoricalDataViewBuilder:To generate the same mapping, you must set the following params to createCategoricalDataViewBuilder method:

([
    {
        source: {
            displayName: "Category",
            queryName: SampleBarChartData.ColumnCategory,
            type: ValueType.fromDescriptor({ text: true }),
            roles: {
                Category: true
            },
        },
        values: this.valuesCategory
    }
],
[
    {
        source: {
            displayName: "Measure",
            isMeasure: true,
            queryName: SampleBarChartData.MeasureColumn,
            type: ValueType.fromDescriptor({ numeric: true }),
            roles: {
                Measure: true
            },
        },
        values: this.valuesMeasure
    },
], columnNames)

Dove this.valuesCategory è una matrice di categorie:Where this.valuesCategory is an array of categories:

public valuesCategory: string[] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

Mentre this.valuesMeasure è una matrice di misure per ogni categoria:And this.valuesMeasure is an array of measures for each category:

public valuesMeasure: number[] = [742731.43, 162066.43, 283085.78, 300263.49, 376074.57, 814724.34, 570921.34];

È ora possibile usare la classe SampleBarChartDataBuilder nello unit test.Now, you can use the SampleBarChartDataBuilder class in your unit test.

La classe ValueType è definita nel pacchetto powerbi-visuals-utils-testutilsThe ValueType class is defined in the powerbi-visuals-utils-testutils package. e il metodo createCategoricalDataViewBuilder richiede la libreria lodash.And the createCategoricalDataViewBuilder method requires the lodash library.

Aggiungere questi pacchetti alle dipendenze.Add these packages to the dependencies.

In package.json nella sezione devDependenciesIn package.json at devDependencies section

"lodash-es": "4.17.1",
"powerbi-visuals-utils-testutils": "2.2.0"

ChiamataCall

npm install

Per installare la libreria lodash-es.to install lodash-es library.

È ora possibile eseguire di nuovo lo unit test.Now, you can run the unit test again. È necessario ottenere l'output seguente:You must get the following output:

> karma start

23 05 2017 16:19:54.318:WARN [watcher]: Pattern "E:/WORKSPACE/PowerBI/PowerBI-visuals-sampleBarChart/data/*.csv" does not match any file.
23 05 2017 16:19:58.333:WARN [karma]: No captured browser, open https://localhost:9876/
23 05 2017 16:19:58.346:INFO [karma]: Karma v1.3.0 server started at https://localhost:9876/
23 05 2017 16:19:58.346:INFO [launcher]: Launching browser Chrome with unlimited concurrency
23 05 2017 16:19:58.394:INFO [launcher]: Starting browser Chrome
23 05 2017 16:19:59.873:INFO [Chrome 58.0.3029 (Windows 10 0.0.0)]: Connected on socket /#NcNTAGH9hWfGMCuEAAAA with id 3551106
Chrome 58.0.3029 (Windows 10 0.0.0): Executed 1 of 1 SUCCESS (1.266 secs / 1.052 secs)

=============================== Coverage summary ===============================
Statements   : 56.72% ( 135/238 )
Branches     : 32.54% ( 41/126 )
Functions    : 66.67% ( 38/57 )
Lines        : 52.83% ( 112/212 )
================================================================================

L'oggetto visivo verrà aperto nel browser Chrome, come illustrato:Your visual opens in the Chrome browser, as shown:

Avvio dello unit test in Chrome

Il riepilogo mostra che la copertura è aumentata.The summary shows that coverage has increased. Per altre informazioni sulla code coverage corrente, aprire coverage\index.html.To learn more about current code coverage, open coverage\index.html.

Indice di code coverage dello unit test

In alternativa, esaminare l'ambito della cartella src:Or look at the scope of the src folder:

Code coverage della cartella src

Nell'ambito del file è possibile visualizzare il codice sorgente.In the scope of file, you can view the source code. Le utilità Coverage evidenzieranno la riga in rosso se un determinato codice non viene eseguito durante gli unit test.The Coverage utilities would highlight the row in red if certain code isn't executed during the unit tests.

Code coverage del file visual.ts

Importante

Il valore della code coverage non significa che la code coverage della funzionalità sia appropriata per l'oggetto visivo.Code coverage doesn't mean that you have good functionality coverage of the visual. Un semplice unit test fornisce il 96% di code coverage in src\visual.ts.One simple unit test provides over 96 percent coverage in src\visual.ts.

Passaggi successiviNext steps

Quando l'oggetto visivo è pronto, è possibile inviarlo per la pubblicazione.When your visual is ready, you can submit it for publication. Per altre informazioni, vedere Pubblicare oggetti visivi di Power BI in AppSource.For more information, see Publish Power BI visuals to AppSource.