Opplæring: Legge til enhetstester for visualobjektprosjekter i Power BI

Denne artikkelen beskriver det grunnleggende om å skrive enhetstester for Power BI-visualobjektene, inkludert hvordan du gjør følgende:

  • Konfigurer Testing-rammeverket for JavaScript-testløper for Karma, Jasmine.
  • Bruk powerbi-visuals-utils-testutils-pakken.
  • Bruk mocks og fakes for å forenkle enhetstesting av Power BI-visualobjekter.

Forutsetning

  • Et installert Power BI visualobjektprosjekt
  • Et konfigurert Node.js-miljø

Eksemplene i denne artikkelen bruker stolpediagrammet for testing.

Installere og konfigurere Karma JavaScript-testløperen og Jasmine

Legg til de nødvendige bibliotekene i package.json-filen i devDependencies delen:

"@types/d3": "5.7.2",
"@types/d3-selection": "^1.0.0",
"@types/jasmine": "^3.10.2",
"@types/jasmine-jquery": "^1.5.34",
"@types/jquery": "^3.5.8",
"@types/karma": "^6.3.1",
"@types/lodash-es": "^4.17.5",
"coveralls": "^3.1.1",
"d3": "5.12.0",
"jasmine": "^3.10.0",
"jasmine-core": "^3.10.1",
"jasmine-jquery": "^2.1.1",
"jquery": "^3.6.0",
"karma": "^6.3.9",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.0.3",
"karma-coverage-istanbul-reporter": "^3.0.3",
"karma-jasmine": "^4.0.1",
"karma-junit-reporter": "^2.0.1",
"karma-sourcemap-loader": "^0.3.8",
"karma-typescript": "^5.5.2",
"karma-typescript-preprocessor": "^0.4.0",
"karma-webpack": "^5.0.0",
"powerbi-visuals-api": "^3.8.4",
"powerbi-visuals-tools": "^3.3.2",
"powerbi-visuals-utils-dataviewutils": "^2.4.1",
"powerbi-visuals-utils-formattingutils": "^4.7.1",
"powerbi-visuals-utils-interactivityutils": "^5.7.1",
"powerbi-visuals-utils-tooltiputils": "^2.5.2",
"puppeteer": "^11.0.0",
"style-loader": "^3.3.1",
"ts-loader": "~8.2.0",
"ts-node": "^10.4.0",
"tslint": "^5.20.1",
"tslint-microsoft-contrib": "^6.2.0"

Hvis du vil ha mer informasjon om package.json, kan du se beskrivelsen på npm-package.json.

Lagre package.json-filen, og kjør følgende kommando på plasseringen til package.json-filen:

npm install

Pakkebehandling installerer alle nye pakker som legges til package.json.

Hvis du vil kjøre enhetstester, konfigurerer du testløperen og konfigurasjonen webpack .

Følgende kode er et eksempel på filen test.webpack.config.js :

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
        })
    ]
};

Følgende kode er et eksempel på karma.conf.ts-filen :

"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 specify Chrome to use the 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"
        }
    });
};

Hvis det er nødvendig, kan du endre denne konfigurasjonen.

Koden i karma.conf.js inneholder følgende variabler:

  • recursivePathToTests: Finner testkoden.

  • srcRecursivePath: Finner JavaScript-koden for utdata etter kompilering.

  • srcCssRecursivePath: Finner utdata-CSS etter kompilering av mindre fil med stiler.

  • srcOriginalRecursivePath: Finner kildekoden for visualobjektet.

  • coverageFolder: Bestemmer hvor dekningsrapporten skal opprettes.

Konfigurasjonsfilen inneholder følgende egenskaper:

  • singleRun: true: Tester kjøres på et system for kontinuerlig integrering (CI), eller de kan kjøres én gang. Du kan endre innstillingen til feilsøking false av testene. Karma-rammeverket holder nettleseren i gang, slik at du kan bruke konsollen til feilsøking.

  • files: [...]: I denne matrisen kan du angi filene som skal lastes inn i nettleseren. Filene du laster inn, er vanligvis kildefiler, testtilfeller og biblioteker (for eksempel Jasmine eller testverktøy). Du kan legge til flere filer etter behov.

  • preprocessors: I denne delen konfigurerer du handlinger som kjører før enhetstestene kjøres. Handlingene kan forhåndskompilere TypeScript til JavaScript, klargjøre kildetilordningsfiler og generere en kodedekningsrapport. Du kan deaktivere coverage når du feilsøker testene. coverage genererer mer kode for kodedekningstesting, noe som kompliserer feilsøkingstester.

Hvis du vil ha beskrivelser av alle Karma-konfigurasjoner, kan du gå til siden Karma Configuration File .

For enkelhets skyld kan du legge til en testkommando scripts i package.json:

{
    "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"
    }
    ...
}

Nå er du klar til å begynne å skrive enhetstestene.

Kontrollere DOM-elementet i visualobjektet

Hvis du vil teste visualobjektet, må du først opprette en forekomst av visualobjektet.

Opprette et visualobjektforekomstverktøy

Legg til en visualBuilder.ts-fil i testmappen ved hjelp av følgende kode:

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

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

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");
  }
}

Metoden build oppretter en forekomst av visualobjektet. mainElement er en hent-metode som returnerer en forekomst av et DOM-element (root document object model) i visualobjektet. Getter er valgfritt, men det gjør det enklere å skrive enhetstesten.

Du har nå en bygg av en forekomst av visualobjektet. La oss skrive testtilfellet. Eksempeltesttilfellet kontrollerer SVG-elementene som opprettes når visualobjektet vises.

Opprette en TypeScript-fil for å skrive testtilfeller

Legg til en visualTest.ts-fil for testtilfellene ved hjelp av følgende kode:

import powerbi from "powerbi-visuals-api";

import { BarChartBuilder } from "./visualBuilder";
import { SampleBarChartDataBuilder } from "./visualData";

import DataView = powerbi.DataView;

describe("BarChart", () => {
  let visualBuilder: BarChartBuilder;
  let dataView: DataView;
  let defaultDataViewBuilder: SampleBarChartDataBuilder;

  beforeEach(() => {
    visualBuilder = new BarChartBuilder(500, 500);
    defaultDataViewBuilder = new SampleBarChartDataBuilder();
    dataView = defaultDataViewBuilder.getDataView();
  });

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

Flere Jasmine-metoder kalles:

  • describe: Beskriver et testtilfelle. I sammenheng med Jasmine-rammeverket describe beskriver du ofte en serie eller en gruppe spesifikasjoner.

  • beforeEach: Kalles for hvert kall av it metoden, som er definert i describe metoden.

  • it: Definerer én enkelt spesifikasjon. Metoden it må inneholde én eller flere expectations.

  • expect: Oppretter en forventning for en spesifikasjon. En spesifikasjon lykkes hvis alle forventninger passerer uten feil.

  • toBeInDOM: Er én av matchers-metodene . Hvis du vil ha mer informasjon om matchers, kan du se Jasmine Namespace: matchers.

Hvis du vil ha mer informasjon om Jasmine, kan du se dokumentasjonssiden for Jasmine Framework.

Start enhetstester

Denne testen kontrollerer at det primære SVG-elementet for visualobjektet finnes når visualobjektet kjøres. Hvis du vil kjøre enhetstesten, skriver du inn følgende kommando i kommandolinjeverktøyet:

npm run test

karma.js kjører testtilfellet i Chrome-nettleseren.

Screenshot of the Chrome browser, which shows that karma dot js is running the test case.

Merk

Du må installere Google Chrome lokalt.

I kommandolinjevinduet får du følgende utdata:

> 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 )
================================================================================

Slik legger du til statiske data for enhetstester

Opprett visualData.ts-filen i testmappen ved hjelp av følgende kode:

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

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

import TestDataViewBuilder = testDataViewBuilder.TestDataViewBuilder;

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

  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;

    return dataView;
  }
}

Klassen SampleBarChartDataBuilder utvider TestDataViewBuilder og implementerer den abstrakte metoden getDataView.

Når du plasserer data i datafeltsamlinger, produserer Power BI et kategorisk dataview objekt som er basert på dataene dine.

Screenshot of Power BI, which shows the data fields buckets are empty.

I enhetstester har du ikke tilgang til Power BI-kjernefunksjoner som du vanligvis bruker til å reprodusere dataene. Du må imidlertid tilordne statiske data til den kategoriske dataview. TestDataViewBuilder Bruk klassen til å tilordne statiske data.

Hvis du vil ha mer informasjon om datavisningstilordning, kan du se DataViewMappings.

getDataView I metoden kaller createCategoricalDataViewBuilder du metoden med dataene.

sampleBarChart I visualobjekt-capabilities.json-filen har dataRoles vi og dataViewMapping objekter:

"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"
                        }
                    }
                ]
            }
        }
    }
],

Hvis du vil generere samme tilordning, må du angi følgende parametere til createCategoricalDataViewBuilder metoden:

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

Hvor this.valuesCategory er en matrise med kategorier:

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

Og this.valuesMeasure er en matrise med mål for hver kategori:

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

Den endelige versjonen av visualData.ts inneholder følgende kode:

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

import { testDataViewBuilder } from "powerbi-visuals-utils-testutils";
import { valueType } from "powerbi-visuals-utils-typeutils";
import ValueType = valueType.ValueType;

import TestDataViewBuilder = testDataViewBuilder.TestDataViewBuilder;

export class SampleBarChartDataBuilder extends TestDataViewBuilder {
  public static CategoryColumn: string = "category";
  public static MeasureColumn: string = "measure";
  public valuesCategory: string[] = [
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday",
    "Sunday",
  ];
  public valuesMeasure: number[] = [
    742731.43, 162066.43, 283085.78, 300263.49, 376074.57, 814724.34, 570921.34,
  ];

  public getDataView(columnNames?: string[]): DataView {
    let dataView: any = this.createCategoricalDataViewBuilder(
      [
        {
          source: {
            displayName: "Category",
            queryName: SampleBarChartDataBuilder.CategoryColumn,
            type: ValueType.fromDescriptor({ text: true }),
            roles: {
              category: true,
            },
          },
          values: this.valuesCategory,
        },
      ],
      [
        {
          source: {
            displayName: "Measure",
            isMeasure: true,
            queryName: SampleBarChartDataBuilder.MeasureColumn,
            type: ValueType.fromDescriptor({ numeric: true }),
            roles: {
              measure: true,
            },
          },
          values: this.valuesMeasure,
        },
      ],
      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;

    return dataView;
  }
}

Nå kan du bruke SampleBarChartDataBuilder klassen i enhetstesten.

Klassen ValueType er definert i powerbi-visuals-utils-testutils-pakken.

Legg til powerbi-visuals-utils-testutils-pakken i avhengighetene. Finn inndelingen package.jsondependencies i filen, og legg til følgende kode:

"powerbi-visuals-utils-testutils": "^2.4.1",

Ring

npm install

for å installere powerbi-visuals-utils-testutils pakken.

Nå kan du kjøre enhetstesten på nytt. Du må få følgende utdata:

> 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 )
================================================================================

Sammendraget viser at dekningen har økt. Hvis du vil lære mer om gjeldende kodedekning, åpner coverage/html-report/index.html du filen.

Screenshot of the browser window, which shows the HTML code coverage report.

Eller se på omfanget av src mappen:

Screenshot of the browser window, which shows the code coverage report for the visual dot ts file.

I filomfanget kan du vise kildekoden. Verktøyene coverage uthever raden i rødt hvis bestemte linjer med kode ikke kjører under enhetstestene.

Screenshot of the visual source code, which shows that the lines of code that didn't run in unit tests are highlighted in red.

Viktig

Kodedekning betyr ikke at du har god funksjonalitetsdekning for visualobjektet. En enkel enhetstest gir over 96 prosent dekning i src/barChart.ts.

Feilsøking

Hvis du vil feilsøke testene via nettleserkonsollen singleRun , endrer du verdien i karma.conf.ts til false. Denne innstillingen vil holde nettleseren i gang når nettleseren starter etter at testene kjøres.

Visualobjektet åpnes i Chrome-nettleseren.

Screenshot of the Chrome browser window, which shows the custom Power BI visual.

Når visualobjektet er klart, kan du sende det inn for publisering. Hvis du vil ha mer informasjon, kan du se Publisere Power BI-visualobjekter til AppSource.