Öğretici: Power BI görsel projeleri için birim testleri ekleme

Bu makalede, Power BI görselleriniz için birim testleri yazmanın temelleri ve aşağıdakiler de açıklanmaktadır:

  • Karma JavaScript test çalıştırıcısı test çerçevesi Jasmine'i ayarlayın.
  • powerbi-visuals-utils-testutils paketini kullanın.
  • Power BI görsellerinin birim testini basitleştirmeye yardımcı olması için moklar ve sahteleri kullanın.

Önkoşullar

  • Yüklü bir Power BI görselleri projesi
  • Yapılandırılmış Node.js ortamı

Bu makaledeki örneklerde test için çubuk grafiği görseli kullanılmaktadır.

Karma JavaScript test çalıştırıcısını ve Jasmine'i yükleme ve yapılandırma

gerekli kitaplıkları devDependencies bölümündeki package.json dosyasına ekleyin:

"@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.jsonhakkında daha fazla bilgi edinmek için npm-package.jsonkonumundaki açıklamaya bakın.

package.json dosyasını kaydedin ve package.json dosyasının konumunda aşağıdaki komutu çalıştırın:

npm install

Paket yöneticisi, package.jsoneklenen tüm yeni paketleri yükler.

Birim testlerini çalıştırmak için test çalıştırıcısını ve webpack yapılandırmasını yapılandırın.

Aşağıdaki kod, test.webpack.config.js dosyasının bir örneğidir:

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

Aşağıdaki kod, test.tsconfig.json dosyasının bir örneğidir:

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

Aşağıdaki kod, karma.conf.ts dosyasının bir örneğidir:

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

Gerekirse bu yapılandırmayı değiştirebilirsiniz.

karma.conf.js kodu aşağıdaki değişkenleri içerir:

  • testRecursivePath: Test kodunu bulur.

  • srcOriginalRecursivePath: Görselinizin kaynak kodunu bulur.

  • coverageFolder: Kapsam raporunun oluşturulacağı yeri belirler.

Yapılandırma dosyası aşağıdaki özellikleri içerir:

  • singleRun: true: Testler sürekli tümleştirme (CI) sisteminde çalıştırılır veya bir kez çalıştırılabilir. Testlerinizin hatalarını ayıklamak için ayarı false olarak değiştirebilirsiniz. Karma çerçevesi, hata ayıklama için konsolunu kullanabilmeniz için tarayıcının çalışır durumda kalmasını sağlar.

  • files: [...]: Bu dizide, tarayıcıya yüklenecek dosyaları belirtebilirsiniz. Yüklediğiniz dosyalar genellikle kaynak dosyalar, test çalışmaları ve kitaplıklardır (Jasmine veya test yardımcı programları gibi). Gerektiğinde daha fazla dosya ekleyebilirsiniz.

  • preprocessors: Bu bölümde, birim testleri çalışmadan önce çalışan eylemleri yapılandıracaksınız. Eylemler TypeScript'i JavaScript'e önceden derleyebilir, kaynak eşleme dosyalarını hazırlayabilir ve bir kod kapsamı raporu oluşturabilir. Testlerinizin hatalarını ayıklarken coverage devre dışı bırakabilirsiniz. coverage, hata ayıklama testlerini karmaşıklaştıran kod kapsamı testi için daha fazla kod oluşturur.

Tüm Karma yapılandırmalarının açıklamaları için Karma Yapılandırma Dosyası sayfasına gidin.

Size kolaylık sağlamak için, package.jsoniçindeki scripts bir test komutu ekleyebilirsiniz:

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

Artık birim testlerinizi yazmaya başlamaya hazırsınız.

Görselin DOM öğesini denetleme

Görseli test etmek için önce görselin bir örneğini oluşturun.

Görsel örnek oluşturucu oluşturma

Aşağıdaki kodu kullanarak test klasörüne bir visualBuilder.ts dosyası ekleyin:

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 yöntemi görselinizin bir örneğini oluşturur. mainElement, görselinizde kök belge nesne modeli (DOM) öğesinin bir örneğini döndüren bir get yöntemidir. Getter işlevi isteğe bağlıdır, ancak birim testini yazmayı kolaylaştırır.

Artık görselinizin bir örneğini derlediniz. Test senaryosu yazalım. Örnek test çalışması, görseliniz görüntülendiğinde oluşturulan SVG öğelerini denetler.

Test çalışmalarını yazmak için TypeScript dosyası oluşturma

Aşağıdaki kodu kullanarak test çalışmaları için bir visualTest.ts dosyası ekleyin:

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

Birkaç Jasmine yöntemi çağrılır:

  • describe: Bir test vakasını açıklar. Jasmine çerçevesi bağlamında, describe genellikle bir belirtim paketini veya grubunu açıklar.

  • beforeEach: describe yönteminde tanımlanan it yönteminin her çağrısından önce çağrılır.

  • it: Tek bir belirtim tanımlar. it yöntemi bir veya daha fazla expectationsiçermelidir.

  • expect: Belirtim için bir beklenti oluşturur. Tüm beklentiler herhangi bir hata olmadan geçerse belirtim başarılı olur.

  • toBeInDOM: eşleştiricilerinin yöntemlerinden biridir. Eşleştiriciler hakkında daha fazla bilgi için, Jasmine Ad Alanı: eşleştiricilerbölümüne bakın.

Jasmine hakkında daha fazla bilgi için Jasmine çerçevesinin dokümantasyon sayfasına bakın.

Birim testleri için statik veri ekleme

Aşağıdaki kodu kullanarak test klasöründe visualData.ts dosyasını oluşturun:

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 sınıfı TestDataViewBuilder genişletir ve getDataViewsoyut yöntemini uygular.

Verileri veri alanı demetlerine yerleştirdiğinizde Power BI, verilerinizi temel alan kategorik bir dataview nesnesi oluşturur.

Veri alanları demetlerinin boş olduğunu gösteren Power BI ekran görüntüsü.

Birim testlerinde normalde verileri yeniden oluşturmak için kullandığınız Power BI çekirdek işlevlerine erişiminiz yoktur. Ancak statik verilerinizi kategorik dataviewile haritalamanız gerekir. Statik verilerinizi eşlemek için TestDataViewBuilder sınıfını kullanın.

Veri Görünümü eşlemesi hakkında daha fazla bilgi için bkz. DataViewMappings.

getDataView yönteminde verilerinizle createCategoricalDataViewBuilder yöntemini çağırırsınız.

sampleBarChart görsel capabilities.json dosyasında dataRoles ve dataViewMapping nesneleri vardır:

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

Aynı eşlemeyi oluşturmak için aşağıdaki parametreleri createCategoricalDataViewBuilder yöntemine ayarlamanız gerekir:

([
    {
        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)

burada this.valuesCategory bir kategori dizisidir:

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

this.valuesMeasure her kategori için bir ölçü dizisidir:

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

visualData.ts son sürümü aşağıdaki kodu içerir:

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 sınıfı powerbi-visuals-utils-typeutils paketinde tanımlanır.

Şimdi birim testini çalıştırabilirsiniz.

Birim testlerini başlatma

Bu test, görsel çalıştırıldığında görselinizin kök SVG öğesinin mevcut olup olmadığını denetler. Birim testini çalıştırmak için komut satırı aracına aşağıdaki komutu girin:

npm run test

karma.js Test çalışmasını Chrome tarayıcısında çalıştırır.

Chrome tarayıcısının karma.js'nin test davasını çalıştırdığını gösteren ekran görüntüsü.

Not

Google Chrome'u yerel olarak yüklemeniz gerekir.

Komut satırı penceresinde aşağıdaki çıkışı alırsınız:

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

Geçerli kod kapsamı hakkında daha fazla bilgi edinmek için coverage/html-report/index.html dosyasını açın.

Görsel nokta ts dosyasının kod kapsamı raporunu gösteren tarayıcı penceresinin ekran görüntüsü.

Dosya kapsamında kaynak kodu görüntüleyebilirsiniz. birim testleri sırasında belirli kod satırları çalışmazsa coverage yardımcı programları satırı kırmızıyla vurgular.

Birim testlerinde çalışmayan kod satırlarının kırmızı renkle vurgulandığını gösteren görsel kaynak kodunun ekran görüntüsü.

Önemli

Kod kapsamı, görselin iyi işlevsellik kapsamına sahip olduğunuz anlamına gelmez. Basit bir birim testi, src/barChart.ts'da yüzde 96'nın üzerinde coverage sağlar.

Hata ayıklama

Tarayıcı konsolu aracılığıyla testlerinizin hatalarını ayıklamak için karma.conf.ts içindeki singleRun değerini falseolarak değiştirin. Bu ayar, testler çalıştırıldıktan sonra tarayıcı başlatıldığında tarayıcınızı çalışır durumda tutar.

Görseliniz Chrome tarayıcısında açılır.

Özel Power BI görselini gösteren Chrome tarayıcı penceresinin ekran görüntüsü.

Görseliniz hazır olduğunda yayına gönderebilirsiniz. Daha fazla bilgi için bkz. Power BI görsellerini AppSourceyayımlama.