Partager via


Tutoriel : Ajouter des tests unitaires pour des projets de visuels Power BI

Cet article décrit les principes fondamentaux de l’écriture de tests unitaires pour vos visuels Power BI, notamment comment :

  • Configurer le framework d’exécuteur de test Karma JavaScript, Jasmine.
  • Utiliser le package powerbi-visuals-utils-testutils.
  • Utiliser des simulacres et des substituts pour aider à simplifier les tests unitaires des visuels Power BI.

Prérequis

  • Un projet de visuels Power BI installé
  • Un environnement Node.js configuré

Les exemples présentés dans cet article utilisent le visuel de graphique à barres à des fins de test.

Installer et configurer l’exécuteur de test Karma JavaScript et Jasmine

Ajoutez les bibliothèques requises au fichier package.json dans la section devDependencies :

"@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"

Pour en savoir plus sur package.json, consultez la description dans npm-package.json.

Enregistrez le fichier package.json, puis exécutez la commande suivante à l’emplacement du fichier package.json :

npm install

Le gestionnaire de package installe tous les nouveaux packages qui sont ajoutés à package.json.

Pour exécuter des tests unitaires, configurez Test Runner et webpack config.

Le code suivant est un exemple de fichier 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
        })
    ]
};

Le code suivant est un exemple de fichier karma.conf.ts :

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

Si nécessaire, vous pouvez modifier cette configuration.

Le code dans karma.conf.js contient les variables suivantes :

  • recursivePathToTests : localise le code de test.

  • srcRecursivePath : localise le code JavaScript de sortie après la compilation.

  • srcCssRecursivePath : localise le CSS de sortie après la compilation d’un fichier LESS avec des styles.

  • srcOriginalRecursivePath : localise le code source de votre visuel.

  • coverageFolder : détermine l’emplacement de création du rapport de couverture.

Le fichier de configuration comprend les propriétés suivantes :

  • singleRun: true : les tests sont exécutés sur un système d’intégration continue (CI), ou peuvent être exécutés une seule fois. Vous pouvez remplacer la valeur du paramètre par false pour déboguer vos tests. L’infrastructure Karma maintient le navigateur en cours d’exécution afin que vous puissiez utiliser la console pour le débogage.

  • files: [...] : dans ce tableau, vous pouvez spécifier les fichiers à charger dans le navigateur. Les fichiers que vous chargez sont généralement des fichiers sources, des cas de test et des bibliothèques (comme Jasmine ou des utilitaires de test). Vous pouvez ajouter d’autres fichiers si nécessaire.

  • preprocessors : dans cette section, vous configurez les actions qui s’exécutent avant l’exécution des tests unitaires. Les actions peuvent précompiler le code TypeScript en JavaScript, préparer les fichiers de mappage sources et générer un rapport de couverture du code. Vous pouvez désactiver coverage quand vous déboguez vos tests. coverage génère plus de code pour tester la couverture du code, ce qui complique les tests de débogage.

Pour obtenir une description de toutes les configurations Karma, accédez à la page sur le fichier de configuration Karma.

Pour plus de commodité, vous pouvez ajouter une commande de test dans les scripts du fichier 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"
    }
    ...
}

Vous êtes maintenant prêt à commencer l’écriture de vos tests unitaires.

Vérifier l’élément DOM du visuel

Pour tester le visuel, commencez par créer une instance de celui-ci.

Créer un générateur d’instances de visuels

Ajoutez un fichier visualBuilder.ts au dossier test à l’aide du code suivant :

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

La build méthode crée une instance de votre visuel. mainElement est une méthode GET, qui retourne une instance de l’élément DOM (Document Object Model) racine dans votre visuel. L’accesseur Get est facultatif, mais facilite l’écriture des tests unitaires.

Vous disposez maintenant d’une build d’une instance de votre visuel. Écrivons le cas de test. L’exemple de cas de test vérifie les éléments SVG qui sont créés lors de l’affichage de votre visuel.

Créer un fichier TypeScript pour écrire des cas de test

Ajoutez un fichier visualTest.ts pour les cas de test à l’aide du code suivant :

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

Plusieurs méthodes Jasmine sont appelées :

  • describe : décrit un cas de test. Dans le contexte de l’infrastructure Jasmine, describe décrit souvent une suite ou un groupe de spécifications.

  • beforeEach : est appelée avant chaque appel de la méthode it, qui est définie dans la méthode describe.

  • it : définit une spécification unique. La méthode it doit contenir un ou plusieurs expectations.

  • expect : crée une attente pour une spécification. Une spécification réussit si toutes les attentes réussissent sans aucun échec.

  • toBeInDOM : est l’une des méthodes matchers. Pour plus d’informations sur les matchers, consultez Jasmine Namespace: matchers.

Pour plus d’informations sur Jasmine, consultez la page de documentation du framework Jasmine.

Lancer des tests unitaires

Ce test vérifie que l’élément SVG racine de votre visuel existe quand le visuel s’exécute. Pour exécuter le test unitaire, entrez la commande suivante dans l’outil en ligne de commande :

npm run test

karma.js exécute le cas de test dans le navigateur Chrome.

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

Remarque

Vous devez installer Google Chrome localement.

Dans la fenêtre de ligne de commande, vous obtenez la sortie suivante :

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

Comment ajouter des données statiques pour les tests unitaires

Créez le fichier visualData.ts dans le dossier test à l’aide du code suivant :

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

La classe SampleBarChartDataBuilder étend TestDataViewBuilder et implémente la méthode abstraite getDataView.

Quand vous placez des données dans des compartiments de champs de données, Power BI produit un objet dataview catégorique basé sur vos données.

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

Dans des tests unitaires, vous n’avez pas accès aux fonctions principales Power BI que vous utilisez normalement pour reproduire les données. Toutefois, vous devez mapper vos données statiques au dataview catégorique. Utilisez la classe TestDataViewBuilder pour mapper vos données statiques.

Pour plus d’informations sur le mappage des vues de données, consultez DataViewMappings.

Dans la méthode getDataView, vous appelez la méthode createCategoricalDataViewBuilder avec vos données.

Dans le fichier capabilities.json du visuel sampleBarChart, nous avons les objets dataRoles et dataViewMapping :

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

Pour générer le même mappage, vous devez définir les paramètres suivants sur la méthode 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)

this.valuesCategory est un tableau de catégories :

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

Et this.valuesMeasure est un tableau de mesures pour chaque catégorie :

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

La version finale de visualData.ts contient le code suivant :

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

À présent, vous pouvez utiliser la classe SampleBarChartDataBuilder dans votre test unitaire.

La classe ValueType est définie dans le package powerbi-visuals-utils-testutils.

Ajoutez le package powerbi-visuals-utils-testutils aux dépendances. Dans le fichier package.json, recherchez la section dependencies et ajoutez le code suivant :

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

Appeler

npm install

pour installer le package powerbi-visuals-utils-testutils.

À présent, vous pouvez réexécuter le test unitaire. Vous devez obtenir la sortie suivante :

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

Le résumé indique que la couverture a augmenté. Pour en savoir plus sur la couverture du code actuelle, ouvrez le fichier coverage/html-report/index.html.

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

Ou examinez l’étendue du dossier src :

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

Dans l’étendue du fichier, vous pouvez voir le code source. Les utilitaires coverage mettent en évidence la ligne en rouge si certaines lignes de code ne s’exécutent pas pendant les tests unitaires.

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

Important

La couverture du code ne signifie pas que vous avez une bonne couverture des fonctionnalités du visuel. Un simple test unitaire fourni plus de 96 pour cent de couverture dans src/barChart.ts.

Débogage

Pour déboguer vos tests via la console du navigateur, modifiez la valeur singleRun dans karma.conf.ts en false. Ce paramètre maintient votre navigateur en cours d’exécution quand il démarre après l’exécution des tests.

Votre visuel s’ouvre dans le navigateur Chrome.

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

Quand votre visuel est prêt, vous pouvez le soumettre pour publication. Pour plus d’informations, consultez Publier des visuels Power BI dans AppSource.