Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym artykule opisano podstawy pisania testów jednostkowych dla wizualizacji usługi Power BI, w tym jak:
- Skonfiguruj platformę testowania modułu uruchamiającego testy Karma JavaScript, Jasmine.
- Użyj pakietu powerbi-visuals-utils-testutils.
- Użyj makiety i podróbek, aby uprościć testowanie jednostkowe wizualizacji usługi Power BI.
Warunki wstępne
- Zainstalowany projekt wizualizacji usługi Power BI
- Skonfigurowane środowisko Node.js
W przykładach w tym artykule do testowania użyto wykresu słupkowego .
Zainstaluj i skonfiguruj tester Karma JavaScript oraz Jasmine
Dodaj wymagane biblioteki do pliku package.json w sekcji devDependencies
:
"@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"
Aby dowiedzieć się więcej na temat package.json, zobacz opis w npm-package.json.
Zapisz plik package.json i uruchom następujące polecenie w lokalizacji pliku package.json:
npm install
Menedżer pakietów instaluje wszystkie nowe pakiety dodane do package.json.
Aby uruchomić testy jednostkowe, skonfiguruj moduł uruchamiający testy i konfigurację webpack
.
Poniższy kod jest przykładem pliku 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
})
]
};
Poniższy kod jest przykładem pliku 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"
]
}
Poniższy kod jest przykładem pliku 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"
}
});
};
W razie potrzeby możesz zmodyfikować tę konfigurację.
Kod w karma.conf.js zawiera następujące zmienne:
testRecursivePath
: lokalizuje kod testowy.srcOriginalRecursivePath
: lokalizuje kod źródłowy wizualizacji.coverageFolder
: określa, gdzie ma zostać utworzony raport pokrycia.
Plik konfiguracji zawiera następujące właściwości:
singleRun: true
: Testy są uruchamiane w systemie ciągłej integracji lub mogą być uruchamiane jednorazowo. Możesz zmienić ustawienie nafalse
na potrzeby debugowania testów. Platforma Karma utrzymuje działanie przeglądarki, aby można było używać konsoli do debugowania.files: [...]
: W tej tablicy można określić pliki do załadowania do przeglądarki. Ładowane pliki to zazwyczaj pliki źródłowe, przypadki testowe i biblioteki (takie jak Jasmine lub narzędzia testowe). W razie potrzeby możesz dodać więcej plików.preprocessors
: W tej sekcji skonfigurujesz akcje uruchamiane przed uruchomieniem testów jednostkowych. Akcje mogą wstępnie skompilować kod TypeScript do języka JavaScript, przygotować pliki mapy źródłowej i wygenerować raport pokrycia kodu. Podczas debugowania testów można wyłączyćcoverage
.coverage
generuje więcej kodu na potrzeby testowania pokrycia kodu, co komplikuje testy debugowania.
Opisy wszystkich konfiguracji Karma można znaleźć na stronie Pliku Konfiguracji Karma.
Dla wygody możesz dodać polecenie testowe do scripts
w package.json:
{
"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"
}
...
}
Teraz możesz rozpocząć pisanie testów jednostkowych.
Sprawdź element DOM wizualizacji
Aby przetestować wizualizację, najpierw utwórz jej wystąpienie.
Utwórz wizualny kreator instancji
Dodaj plik visualBuilder.ts do folderu test przy użyciu następującego kodu:
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");
}
}
Metoda build
tworzy instancję twojego elementu wizualnego.
mainElement
to metoda get, która zwraca wystąpienie elementu głównego modelu obiektu dokumentu (DOM) w wizualizacji. Moduł pobierania jest opcjonalny, ale ułatwia pisanie testu jednostkowego.
Masz teraz wersję instancji swojej wizualizacji. Napiszmy przypadek testowy. Przykładowy przypadek testowy sprawdza elementy SVG tworzone podczas wyświetlania wizualizacji.
Tworzenie pliku TypeScript do zapisywania przypadków testowych
Dodaj plik visualTest.ts dla przypadków testowych przy użyciu następującego kodu:
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();
});
});
});
Wywołuje się kilka metod Jasmine:
describe
: opisuje przypadek testowy. W kontekście platformy Jasminedescribe
często opisuje zestaw lub grupę specyfikacji.beforeEach
: jest wywoływana przed każdym wywołaniem metodyit
, która jest zdefiniowana w metodziedescribe
.it
: definiuje pojedynczą specyfikację. Metodait
powinna zawierać co najmniej jednąexpectations
.expect
: Tworzy oczekiwanie wobec specyfikacji. Specyfikacja powiedzie się, jeśli wszystkie oczekiwania zostaną spełnione bez żadnych błędów.toBeInDOM
: jest jedną z metod dopasowywania . Aby uzyskać więcej informacji na temat matcherów, zobacz Jasmine Namespace: matchers.
Aby uzyskać więcej informacji na temat platformy Jasmine, zobacz stronę dokumentacji platformy
Jak dodać dane statyczne na potrzeby testów jednostkowych
Utwórz plik visualData.ts w folderze test przy użyciu następującego kodu:
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;
}
}
Klasa SampleBarChartDataBuilder
rozszerza TestDataViewBuilder
i implementuje metodę abstrakcyjną getDataView
.
Gdy dane są umieszczane w zasobnikach pól danych, usługa Power BI tworzy obiekt dataview
kategorii oparty na danych.
W testach jednostkowych nie masz dostępu do podstawowych funkcji usługi Power BI, których zwykle używasz do odtworzenia danych. Trzeba jednak odwzorować dane statyczne na kategorię dataview
. Użyj klasy TestDataViewBuilder
, aby zamapować dane statyczne.
Aby uzyskać więcej informacji na temat mapowania widoku danych, zobacz DataViewMappings.
W metodzie getDataView
wywołujesz metodę createCategoricalDataViewBuilder
z danymi.
W wizualizacji pliku sampleBarChart
capabilities.json mamy obiekty dataRoles
i 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"
}
}
]
}
}
}
],
Aby wygenerować to samo mapowanie, należy ustawić następujące parametry na metodę 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)
Gdzie this.valuesCategory
jest tablicą kategorii:
public valuesCategory: string[] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
A this.valuesMeasure
to tablica miar dla każdej kategorii:
public valuesMeasure: number[] = [742731.43, 162066.43, 283085.78, 300263.49, 376074.57, 814724.34, 570921.34];
Ostateczna wersja visualData.ts zawiera następujący kod:
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;
}
}
Klasa ValueType
jest zdefiniowana w pakiecie powerbi-visuals-utils-typeutils.
Teraz możesz uruchomić test jednostkowy.
Uruchamianie testów jednostkowych
Ten test sprawdza, czy główny element SVG wizualizacji istnieje po uruchomieniu wizualizacji. Aby uruchomić test jednostkowy, wprowadź następujące polecenie w narzędziu wiersza polecenia:
npm run test
karma.js
uruchamia przypadek testowy w przeglądarce Chrome.
Notatka
Musisz zainstalować przeglądarkę Google Chrome lokalnie.
W oknie wiersza polecenia uzyskasz następujące dane wyjściowe:
> 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 )
================================================================================
Aby dowiedzieć się więcej na temat bieżącego pokrycia kodu, otwórz plik coverage/html-report/index.html
.
W zakresie pliku można wyświetlić kod źródłowy. Narzędzia coverage
wyróżniają wiersz na czerwono, jeśli niektóre wiersze kodu nie są uruchamiane podczas testów jednostkowych.
Ważny
Pokrycie kodu nie oznacza, że masz dobre pokrycie funkcjonalności wizualizacji. Jeden prosty test jednostkowy daje ponad 96 procent pokrycia w src/barChart.ts
.
Debugowanie
Aby debugować testy za pomocą konsoli przeglądarki, zmień wartość singleRun
w karma.conf.ts na false
. To ustawienie spowoduje, że przeglądarka będzie działać po uruchomieniu po zakończeniu testów.
Wizualizacja zostanie otwarta w przeglądarce Chrome.
Powiązana zawartość
Gdy wizualizacja będzie gotowa, możesz przesłać ją do publikacji. Aby uzyskać więcej informacji, zobacz Publikowanie wizualizacji usługi Power BI w usłudze AppSource.