英語で読む

次の方法で共有


チュートリアル: Power BI ビジュアル プロジェクトの単体テストを追加する

この記事では、次の方法を含む、Power BI ビジュアルの単体テストの記述の基本について説明します。

  • Karma JavaScript テスト ランナー テスト フレームワーク 、ジャスミンを設定します。
  • powerbi-visuals-utils-testutils パッケージを使用します。
  • モックと偽物を使用して、Power BI ビジュアルの単体テストを簡略化します。

前提 条件

  • インストールされている Power BI ビジュアル プロジェクト
  • 構成済みの Node.js 環境

この記事の例では、テスト用の 横棒グラフ ビジュアルを使用します。

Karma JavaScript テスト ランナーとジャスミンをインストールして構成する

devDependencies セクションの package.json ファイルに必要なライブラリを追加します。

"@types/jasmine": "^5.1.5",
"@types/karma": "^6.3.9",
"coverage-istanbul-loader": "^3.0.5",
"jasmine": "^5.5.0",
"karma": "^6.4.4",
"karma-chrome-launcher": "^3.2.0",
"karma-coverage": "^2.2.1",
"karma-coverage-istanbul-reporter": "^3.0.3",
"karma-jasmine": "^5.1.0",
"karma-junit-reporter": "^2.0.1",
"karma-sourcemap-loader": "^0.4.0",
"karma-typescript": "^5.5.4",
"karma-typescript-preprocessor": "^0.4.0",
"karma-webpack": "^5.0.1",
"playwright-chromium": "^1.49.0",
"powerbi-visuals-api": "~5.11.0",
"powerbi-visuals-tools": "^5.6.0",
"powerbi-visuals-utils-testutils": "6.1.1",
"powerbi-visuals-utils-typeutils": "6.0.3",
"style-loader": "^4.0.0",
"ts-loader": "~9.5.1"

package.jsonの詳細については、npm-package.jsonの説明を参照してください。

package.json ファイルを保存し、package.json ファイルの場所で次のコマンドを実行します。

npm install

パッケージ マネージャーは、package.jsonに追加されたすべての新しいパッケージをインストールします。

単体テストを実行するには、テスト ランナーと webpack 構成を構成します。

次のコードは、test.webpack.config.js ファイルのサンプルです。

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

module.exports = {
    devtool: 'source-map',
    mode: 'development',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            },
            {
                test: /\.json$/,
                loader: 'json-loader'
            },
            {
                test: /\.tsx?$/i,
                enforce: 'post',
                include: path.resolve(__dirname, 'src'),
                exclude: /(node_modules|resources\/js\/vendor)/,
                loader: 'coverage-istanbul-loader',
                options: { esModules: true }
            },
            {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'less-loader',
                        options: {
                            lessOptions: {
                                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
        })
    ]
};

次のコードは、test.tsconfig.json ファイルのサンプルです。

{
  "compilerOptions": {
    "allowJs": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2022",
    "sourceMap": true,
    "outDir": "./.tmp/build/",
    "sourceRoot": "../../src/",
    "moduleResolution": "node",
    "declaration": true,
    "lib": [
      "es2022",
      "dom"
  ]
  },
  "files": [
    "./test/visualTest.ts"
  ],
  "include": [
      "src/*.ts"
  ]
}

次のコードは、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("playwright-chromium").chromium.executablePath();

module.exports = (config) => {
    config.set({
        mode: "development",
        browserNoActivityTimeout: 100000,
        browsers: ["ChromeHeadless"], // or specify Chrome to use the locally installed Chrome browser
        colors: true,
        frameworks: ["jasmine", "webpack"],
        reporters: [
            "progress",
            "junit",
            "coverage",
            "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: [
            testRecursivePath,
            {
                pattern: srcOriginalRecursivePath,
                included: false,
                served: true
            },
            {
                pattern: './capabilities.json',
                watched: false,
                served: true,
                included: false
            }
        ],
        preprocessors: {
            [testRecursivePath]: ["webpack"]
        },
        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: {
            type: "html",
            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"
        }
    });
};

必要に応じて、この構成を変更できます。

karma.conf.js のコードには、次の変数が含まれています。

  • testRecursivePath: テスト コードを検索します。

  • srcOriginalRecursivePath: ビジュアルのソース コードを検索します。

  • coverageFolder: カバレッジ レポートを作成する場所を指定します。

構成ファイルには、次のプロパティが含まれています。

  • singleRun: true: テストは継続的インテグレーション (CI) システムで実行されるか、1 回実行できます。 テストをデバッグするために、設定を false に変更できます。 Karma フレームワークは、デバッグにコンソールを使用できるようにブラウザーを実行し続けます。

  • files: [...]: この配列では、ブラウザーに読み込むファイルを指定できます。 読み込むファイルは、通常、ソース ファイル、テスト ケース、ライブラリ (ジャスミンやテスト ユーティリティなど) です。 必要に応じて、さらにファイルを追加できます。

  • preprocessors: このセクションでは、単体テストを実行する前に実行するアクションを構成します。 このアクションでは、TypeScript を JavaScript にプリコンパイルし、ソース マップ ファイルを準備し、コード カバレッジ レポートを生成できます。 テストをデバッグするときに、coverage を無効にすることができます。 coverage では、コード カバレッジ テスト用のコードが生成され、デバッグ テストが複雑になります。

すべての Karma 構成の説明については、「Karma 構成ファイルの」ページを参照してください。

便宜上、package.jsonscripts にテスト コマンドを追加できます。

{
    "scripts": {
        "pbiviz": "pbiviz",
        "start": "pbiviz start",
        "package": "pbiviz package",
        "pretest": "pbiviz package --resources --no-minify --no-pbiviz",
        "test": "karma start",
        "debug": "karma start --single-run=false --browsers=Chrome"
    }
    ...
}

これで、単体テストの作成を開始する準備ができました。

ビジュアルの DOM 要素を確認する

ビジュアルをテストするには、まずビジュアルのインスタンスを作成します。

ビジュアル インスタンス ビルダーを作成する

次のコードを使用して、visualBuilder.ts ファイルを テスト フォルダーに追加します。

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(): SVGElement | null {
    return this.element.querySelector("svg.barChart");
  }
}

build メソッドは、ビジュアルのインスタンスを作成します。 mainElement は get メソッドであり、ビジュアル内のルート ドキュメント オブジェクト モデル (DOM) 要素のインスタンスを返します。 getter は省略可能ですが、単体テストの記述が簡単になります。

これで、ビジュアルのインスタンスのビルドが作成されました。 テスト ケースを記述してみましょう。 このテスト ケースの例では、ビジュアルが表示されるときに作成される SVG 要素を確認します。

テスト ケースを記述する TypeScript ファイルを作成する

次のコードを使用して、テスト ケースの visualTest.ts ファイルを追加します。

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(document.body.contains(visualBuilder.mainElement)).toBeTruthy();
    });
  });
});

複数の Jasmine メソッドが呼び出されます。

  • describe: テスト ケースについて説明します。 ジャスミン フレームワークのコンテキストでは、describe は多くの場合、一連の仕様を記述します。

  • beforeEach: describe メソッドで定義されている it メソッドの各呼び出しの前に呼び出されます。

  • it: 1 つのスペックを定義します。it メソッドには、1 つ以上の expectationsが含まれている必要があります。

  • expect: スペックに対する期待値を作成します。仕様は、すべての期待値が失敗せずに成功した場合に成功します。

  • toBeInDOM: マッチャーのメソッド の 1 つです。 マッチャーの詳細については、Jasmine の名前空間のマッチャーに関するページを参照してください。

ジャスミンフレームワークのドキュメントについて詳細を知りたい場合は、のドキュメントページをご覧ください。

単体テストの静的データを追加する方法

次のコードを使用して、テスト フォルダーに visualData.ts ファイルを作成します。

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

SampleBarChartDataBuilder クラスは TestDataViewBuilder を拡張し、抽象メソッド getDataView実装します。

データをデータ フィールド バケットに格納すると、Power BI によって、データに基づくカテゴリ dataview オブジェクトが生成されます。

データ フィールドバケットが空であることを示す Power BI のスクリーンショット。

単体テストでは、データの再現に通常使用する Power BI コア関数にアクセスすることはできません。 ただし、静的データをカテゴリ dataviewにマップする必要があります。 静的データをマップするには、TestDataViewBuilder クラスを使用します。

データ ビュー マッピングの詳細については、「DataViewMappings」を参照してください。

getDataView メソッドでは、createCategoricalDataViewBuilder メソッドをデータと共に呼び出します。

sampleBarChart ビジュアル capabilities.json ファイルには、dataRoles オブジェクトと dataViewMapping オブジェクトがあります。

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

同じマッピングを生成するには、次のパラメーターを 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 はカテゴリの配列です。

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

this.valuesMeasure は、各カテゴリの指標の配列です。

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

visualData.ts の最終バージョンには、次のコードが含まれています。

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

ValueType クラスは、powerbi-visuals-utils-typeutils パッケージで定義されています。

これで、単体テストを実行できます。

単体テストを起動する

このテストでは、ビジュアルの実行時に、ビジュアルのルート SVG 要素が存在することを確認します。 単体テストを実行するには、コマンド ライン ツールで次のコマンドを入力します。

npm run test

karma.js Chrome ブラウザーでテスト ケースを実行します。

chrome ブラウザーのスクリーンショット。karma dot js がテスト ケースを実行していることを示しています。

注意

Google Chrome をローカルにインストールする必要があります。

コマンド ライン ウィンドウでは、次の出力が表示されます。

> karma start

Webpack bundling...
assets by status 8.31 KiB [compared for emit]
  assets by path ../build/test/*.ts 1020 bytes
    asset ../build/test/visualData.d.ts 512 bytes [compared for emit]
    asset ../build/test/visualBuilder.d.ts 499 bytes [compared for emit]
    asset ../build/test/visualTest.d.ts 11 bytes [compared for emit]
  assets by path ../build/src/*.ts 6.67 KiB
    asset ../build/src/barChart.d.ts 4.49 KiB [compared for emit]
    asset ../build/src/barChartSettingsModel.d.ts 2.18 KiB [compared for emit]
  asset visualTest.3941401795.js 662 bytes [compared for emit] (name: visualTest.3941401795) 1 related asset
assets by status 2.48 MiB [emitted]
  asset commons.js 2.48 MiB [emitted] (name: commons) (id hint: commons) 1 related asset
  asset runtime.js 6.48 KiB [emitted] (name: runtime) 1 related asset
Entrypoint visualTest.3941401795 2.48 MiB (2.34 MiB) = runtime.js 6.48 KiB commons.js 2.48 MiB visualTest.3941401795.js 662 bytes 3 auxiliary assets        
webpack 5.97.0 compiled successfully in 3847 ms
04 12 2024 11:01:19.255:INFO [karma-server]: Karma v6.4.4 server started at http://localhost:9876/
04 12 2024 11:01:19.257:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
04 12 2024 11:01:19.277:INFO [launcher]: Starting browser ChromeHeadless
04 12 2024 11:01:20.634:INFO [Chrome Headless 131.0.0.0 (Windows 10)]: Connected on socket QYSj9NyHQ14QjFBoAAAB with id 9616879
Chrome Headless 131.0.0.0 (Windows 10): Executed 1 of 1 SUCCESS (0.016 secs / 0.025 secs)
TOTAL: 1 SUCCESS
TOTAL: 1 SUCCESS

=============================== Coverage summary ===============================
Statements   : 66.07% ( 187/283 )
Branches     : 34.88% ( 45/129 )
Functions    : 52.85% ( 37/70 )
Lines        : 65.83% ( 185/281 )
================================================================================

現在のコード カバレッジの詳細については、coverage/html-report/index.html ファイルを開きます。

ビジュアル ドット ts ファイルのコード カバレッジ レポートを示すブラウザー ウィンドウのスクリーンショット。

ファイル スコープでは、ソース コードを表示できます。 coverage ユーティリティでは、単体テスト中に特定のコード行が実行されない場合、行が赤で強調表示されます。

単体テストで実行されなかったコード行が赤で強調表示されていることを示すビジュアル ソース コードのスクリーンショット。

重要

コード カバレッジは視覚化の機能のカバレッジが十分であることを意味するものではありません。 1 つの簡単な単体テストで、src/barChart.ts の 96% 以上のカバレッジが提供されます。

デバッグ

ブラウザー コンソールを使用してテストをデバッグするには、karma.conf.tssingleRun 値を falseに変更します。 この設定は、テストの実行後にブラウザーが起動したときにブラウザーの実行を維持します。

Chrome ブラウザーでビジュアルが開きます。

カスタム Power BI ビジュアルを示す Chrome ブラウザー ウィンドウのスクリーンショット。

ビジュアルの準備ができたら、公開用に提出できます。 詳細については、「Power BI ビジュアルを AppSourceに発行する」を参照してください。


その他のリソース

ドキュメント

トレーニング

モジュール

Playwright で初めてのエンドツーエンド テストをビルドする - Training

このモジュールでは、Playwright を使用して Web アプリケーションのサンプルをテストする方法を学習します。 テストの実行方法、テスト レポートの表示方法、Playwright プロジェクトの構造を学習します。 また、Visual Studio Code を使用して、テストの実行、テストのデバッグ、新しいテストの記録を行います。 最後に、新しいテスト スイートを作成する方法と、テストを調整する方法を学習します。