この記事では、次の方法を含む、Power BI ビジュアルの単体テストの記述の基本について説明します。
- Karma JavaScript テスト ランナー テスト フレームワーク 、ジャスミンを設定します。
- powerbi-visuals-utils-testutils パッケージを使用します。
- モックと偽物を使用して、Power BI ビジュアルの単体テストを簡略化します。
- インストールされている Power BI ビジュアル プロジェクト
- 構成済みの Node.js 環境
この記事の例では、テスト用の 横棒グラフ ビジュアルを使用します。
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.jsonの scripts
にテスト コマンドを追加できます。
{
"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"
}
...
}
これで、単体テストの作成を開始する準備ができました。
ビジュアルをテストするには、まずビジュアルのインスタンスを作成します。
次のコードを使用して、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 コア関数にアクセスすることはできません。 ただし、静的データをカテゴリ 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 ブラウザーでテスト ケースを実行します。
注意
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
ファイルを開きます。
ファイル スコープでは、ソース コードを表示できます。 coverage
ユーティリティでは、単体テスト中に特定のコード行が実行されない場合、行が赤で強調表示されます。
重要
コード カバレッジは視覚化の機能のカバレッジが十分であることを意味するものではありません。 1 つの簡単な単体テストで、src/barChart.ts
の 96% 以上のカバレッジが提供されます。
ブラウザー コンソールを使用してテストをデバッグするには、karma.conf.ts の singleRun
値を false
に変更します。 この設定は、テストの実行後にブラウザーが起動したときにブラウザーの実行を維持します。
Chrome ブラウザーでビジュアルが開きます。
ビジュアルの準備ができたら、公開用に提出できます。 詳細については、「Power BI ビジュアルを AppSourceに発行する」を参照してください。