メインコンテンツ

カスタム プラグインを使ったテストの並列実行

R2026b

この例では、テストの並列実行をサポートするカスタム プラグインの作成方法を説明します。カスタム プラグインは、テスト スイートのパスと失敗のアサーション数をカウントします。TestRunner インスタンスを拡張するため、プラグインは matlab.unittest.plugins.TestRunnerPlugin クラスの特定メソッドをオーバーライドします。さらに、テストの並列実行をサポートするため、プラグインは matlab.unittest.plugins.Parallelizable インターフェイスをサブクラス化します。テストの並列実行には、Parallel Computing Toolbox™ が必要です。

プラグイン クラスの作成

現在のフォルダー内の AssertionCountingPlugin.m という名前のファイルに、TestRunnerPlugin クラスと Parallelizable クラスの両方から継承する、並列化可能なプラグイン クラス AssertionCountingPlugin を作成します。AssertionCountingPlugin の完全なコードについては、プラグイン クラス定義を参照してください。

パスと失敗のアサーション数を追跡するには、properties ブロック内で 4 つの読み取り専用プロパティを定義します。現在の並列プールの MATLAB® ワーカーはそれぞれ NumPassingAssertions と NumFailingAssertions を使用して、配列 TestSuite の一部を実行するときに、パスと失敗のアサーション数を追跡します。MATLAB クライアントは FinalizedNumPassingAssertions と FinalizedNumFailingAssertions を使用して異なるワーカーからの結果を集計し、テスト セッションの最後にパスと失敗のアサーション数の合計をレポートします。

    properties (SetAccess = private)
        NumPassingAssertions
        NumFailingAssertions
        FinalizedNumPassingAssertions
        FinalizedNumFailingAssertions
    end

テスト セッションの実行の拡張

配列 TestSuite 全体の実行を拡張するには、protected アクセスが指定された methods ブロック内で runSession メソッドをオーバーライドします。runSession は Test 要素数の合計についての情報を表示し、プラグインでテキスト出力の生成に使用するプロパティを初期化し、スーパークラス メソッドを呼び出してテスト実行全体をトリガーします。テスト フレームワークがスーパークラス メソッドの評価を完了すると、runSession は、補助メソッド printAssertionSummary (補助メソッドの定義を参照) を呼び出すことによりアサーション カウントの概要を表示します。フレームワークはこのメソッドをクライアント上で 1 回評価します。

    methods (Access = protected)
        function runSession(plugin,pluginData)
            suiteSize = numel(pluginData.TestSuite);
            fprintf("## Running a total of %d tests\n\n",suiteSize)
            plugin.FinalizedNumPassingAssertions = 0;
            plugin.FinalizedNumFailingAssertions = 0;

            runSession@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData)

            fprintf("## Done running tests\n")
            plugin.printAssertionSummary
        end
    end

共有テスト フィクスチャとテスト ケースの作成の拡張

アサーションをカウントするためにイベント AssertionPassed および AssertionFailed にリスナーを追加します。これらのリスナーを追加するには、テスト フレームワークで使用するメソッドを拡張してテスト内容を作成します。テスト内容には、Test 要素ごとの TestCase インスタンス、TestClassSetup および TestClassTeardown の methods ブロックに対するクラス レベルの TestCase インスタンス、および TestCase クラスが属性 SharedTestFixtures をもつ場合に使用する Fixture インスタンスが含まれます。protected アクセスが指定された methods ブロックにこれらの作成メソッドを追加します。

作成メソッドをオーバーライドする際に、対応するスーパークラスを呼び出します。作成メソッドは、テスト フレームワークがそれぞれのコンテキストに対して作成する内容を返します。これらの各メソッドを incrementPassingAssertionsCount および incrementFailingAssertionsCount 補助メソッドを使用して実装する場合、プラグインで必要なリスナーを、返された Fixture または TestCase インスタンスに追加します。

    methods (Access = protected)
        function fixture = createSharedTestFixture(plugin,pluginData)
            fixture = createSharedTestFixture@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData);

            fixture.addlistener("AssertionPassed", ...
                @(~,~)plugin.incrementPassingAssertionsCount);
            fixture.addlistener("AssertionFailed", ...
                @(~,~)plugin.incrementFailingAssertionsCount);
        end

        function testCase = createTestClassInstance(plugin,pluginData)
            testCase = createTestClassInstance@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData);

            testCase.addlistener("AssertionPassed", ...
                @(~,~)plugin.incrementPassingAssertionsCount);
            testCase.addlistener("AssertionFailed", ...
                @(~,~)plugin.incrementFailingAssertionsCount);
        end

        function testCase = createTestMethodInstance(plugin,pluginData)
            testCase = createTestMethodInstance@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData);

            testCase.addlistener("AssertionPassed", ...
                @(~,~)plugin.incrementPassingAssertionsCount);
            testCase.addlistener("AssertionFailed", ...
                @(~,~)plugin.incrementFailingAssertionsCount);
        end
    end

テスト スイートの一部の実行の拡張

テスト フレームワークは配列 TestSuite 全体をグループに分割し、それらをワーカーに割り当てて処理します。各ワーカーは、テスト スイートの部分を 1 つ以上実行できます。ワーカーの動作をカスタマイズするには、protected アクセスが指定された methods ブロック内で runTestSuite メソッドをオーバーライドします。

テスト ランナーを拡張し、ワーカーが実行する各テスト グループの識別子を、グループ内の Test 要素数とともに表示します。さらに、パスと失敗のアサーション数を通信バッファーに格納し、確定した結果を生成するためにクライアントでこれらの値を取得できるようにします。すべてのプラグイン メソッドと同様に、runTestSuite メソッドでは対応するスーパークラス メソッドを適切な時点で呼び出す必要があります。この場合は、プロパティの初期化後かつワーカー データの保存前に、スーパークラス メソッドを呼び出します。テスト フレームワークは、ワーカーの runTestSuite をテスト スイートの部分の数と同数回評価します。

    methods (Access = protected)
        function runTestSuite(plugin,pluginData)
            suiteSize = numel(pluginData.TestSuite);
            groupNumber = pluginData.Group;
            fprintf("### Running a total of %d tests in group %d\n", ...
                suiteSize,groupNumber)
            plugin.NumPassingAssertions = 0;
            plugin.NumFailingAssertions = 0;

            runTestSuite@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData)

            assertionStruct = struct( ...
                "Passing",plugin.NumPassingAssertions, ...
                "Failing",plugin.NumFailingAssertions);
            plugin.storeIn(pluginData.CommunicationBuffer,assertionStruct)
        end
    end

テスト特有のデータを保存するため、runTestSuite の実装には Parallelizable インターフェイスの storeIn メソッドの呼び出しが含まれています。ワーカーがクライアントにレポートしなければならない場合は、storeIn を retrieveFrom とともに使用します。この例では、スーパークラス メソッドから戻った後、NumPassingAssertions と NumFailingAssertions にはテストのグループに対応するパスと失敗のアサーション数が含まれています。storeIn はワーカー データを唯一の入力引数として受け入れるため、構造体 assertionStruct は 2 つのフィールドを使用してアサーションのカウント数をグループ化します。

テスト スイートの確定部分のレポートの拡張

reportFinalizedSuite を拡張して、テスト スイートの確定した各部分のテスト データを取得することにより、アサーションのカウント数を集計します。テスト スイートの部分の保存された構造体を取得するには、reportFinalizedSuite のスコープ内で retrieveFrom メソッドを呼び出します。致命的なアサーション エラーによっていずれのワーカーもバッファーに書き込めない場合など、バッファーが空の場合に既定のデータを返すには (R2024a 以降)、retrieveFrom メソッドを呼び出すときに DefaultData を指定します。フィールドの値を対応するクラス プロパティに追加し、スーパークラス メソッドを呼び出します。テスト フレームワークは、クライアント上でこのメソッドをテスト スイートの部分の数と同数回評価します。

    methods (Access = protected)
        function reportFinalizedSuite(plugin,pluginData)
            assertionStruct = plugin.retrieveFrom( ...
                pluginData.CommunicationBuffer, ...
                DefaultData=struct("Passing",0,"Failing",0));
            plugin.FinalizedNumPassingAssertions = ...
                plugin.FinalizedNumPassingAssertions + assertionStruct.Passing;
            plugin.FinalizedNumFailingAssertions = ...
                plugin.FinalizedNumFailingAssertions + assertionStruct.Failing;

            reportFinalizedSuite@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData)
        end
    end

補助メソッドの定義

private アクセスが指定された methods ブロックで、3 つの補助メソッドを定義します。これらのメソッドは、実行中のテスト スイートの各部分内でパスまたは失敗のアサーション数をインクリメントし、アサーション カウントの概要を出力します。

    methods (Access = private)
        function incrementPassingAssertionsCount(plugin)
            plugin.NumPassingAssertions = plugin.NumPassingAssertions + 1;
        end

        function incrementFailingAssertionsCount(plugin)
            plugin.NumFailingAssertions = plugin.NumFailingAssertions + 1;
        end

        function printAssertionSummary(plugin)
            fprintf("%s\n",repmat('_',1,30))
            fprintf("Total Assertions: %d\n", ...
                plugin.FinalizedNumPassingAssertions + ...
                plugin.FinalizedNumFailingAssertions)
            fprintf("\t%d Passed, %d Failed\n", ...
                plugin.FinalizedNumPassingAssertions, ...
                plugin.FinalizedNumFailingAssertions)
        end
    end

プラグイン クラス定義

以下のコードは、AssertionCountingPlugin クラスの内容全体を示しています。

classdef AssertionCountingPlugin < ...
        matlab.unittest.plugins.TestRunnerPlugin & ...
        matlab.unittest.plugins.Parallelizable

    properties (SetAccess = private)
        NumPassingAssertions
        NumFailingAssertions
        FinalizedNumPassingAssertions
        FinalizedNumFailingAssertions
    end

    methods (Access = protected)
        function runSession(plugin,pluginData)
            suiteSize = numel(pluginData.TestSuite);
            fprintf("## Running a total of %d tests\n\n",suiteSize)
            plugin.FinalizedNumPassingAssertions = 0;
            plugin.FinalizedNumFailingAssertions = 0;

            runSession@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData)

            fprintf("## Done running tests\n")
            plugin.printAssertionSummary
        end

        function fixture = createSharedTestFixture(plugin,pluginData)
            fixture = createSharedTestFixture@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData);

            fixture.addlistener("AssertionPassed", ...
                @(~,~)plugin.incrementPassingAssertionsCount);
            fixture.addlistener("AssertionFailed", ...
                @(~,~)plugin.incrementFailingAssertionsCount);
        end

        function testCase = createTestClassInstance(plugin,pluginData)
            testCase = createTestClassInstance@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData);

            testCase.addlistener("AssertionPassed", ...
                @(~,~)plugin.incrementPassingAssertionsCount);
            testCase.addlistener("AssertionFailed", ...
                @(~,~)plugin.incrementFailingAssertionsCount);
        end

        function testCase = createTestMethodInstance(plugin,pluginData)
            testCase = createTestMethodInstance@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData);

            testCase.addlistener("AssertionPassed", ...
                @(~,~)plugin.incrementPassingAssertionsCount);
            testCase.addlistener("AssertionFailed", ...
                @(~,~)plugin.incrementFailingAssertionsCount);
        end

        function runTestSuite(plugin,pluginData)
            suiteSize = numel(pluginData.TestSuite);
            groupNumber = pluginData.Group;
            fprintf("### Running a total of %d tests in group %d\n", ...
                suiteSize,groupNumber)
            plugin.NumPassingAssertions = 0;
            plugin.NumFailingAssertions = 0;

            runTestSuite@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData)

            assertionStruct = struct( ...
                "Passing",plugin.NumPassingAssertions, ...
                "Failing",plugin.NumFailingAssertions);
            plugin.storeIn(pluginData.CommunicationBuffer,assertionStruct)
        end

        function reportFinalizedSuite(plugin,pluginData)
            assertionStruct = plugin.retrieveFrom( ...
                pluginData.CommunicationBuffer, ...
                DefaultData=struct("Passing",0,"Failing",0));
            plugin.FinalizedNumPassingAssertions = ...
                plugin.FinalizedNumPassingAssertions + assertionStruct.Passing;
            plugin.FinalizedNumFailingAssertions = ...
                plugin.FinalizedNumFailingAssertions + assertionStruct.Failing;

            reportFinalizedSuite@ ...
                matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData)
        end
    end

    methods (Access = private)
        function incrementPassingAssertionsCount(plugin)
            plugin.NumPassingAssertions = plugin.NumPassingAssertions + 1;
        end

        function incrementFailingAssertionsCount(plugin)
            plugin.NumFailingAssertions = plugin.NumFailingAssertions + 1;
        end

        function printAssertionSummary(plugin)
            fprintf("%s\n",repmat('_',1,30))
            fprintf("Total Assertions: %d\n", ...
                plugin.FinalizedNumPassingAssertions + ...
                plugin.FinalizedNumFailingAssertions)
            fprintf("\t%d Passed, %d Failed\n", ...
                plugin.FinalizedNumPassingAssertions, ...
                plugin.FinalizedNumFailingAssertions)
        end
    end
end

テスト クラスの作成

現在のフォルダー内の ExampleTest.m という名前のファイルで、パラメーター化されたテスト クラス ExampleTest を作成します。このクラスには 300 個のテストが含まれ、そのうち 100 個は 1 ~ 10 の整数の疑似乱数を比較するアサーションになります。

classdef ExampleTest < matlab.unittest.TestCase
    properties (TestParameter)
        num1 = repmat({@()randi(10)},1,10)
        num2 = repmat({@()randi(10)},1,10)
    end

    methods (Test)
        function testAssert(testCase,num1,num2)
            testCase.assertNotEqual(num1(),num2())
        end
        function testVerify(testCase,num1,num2)
            testCase.verifyNotEqual(num1(),num2())
        end
        function testAssume(testCase,num1,num2)
            testCase.assumeNotEqual(num1(),num2())
        end
    end
end

テスト ランナーへのプラグインの追加およびテストの実行

ExampleTest クラスからテスト スイートを作成します。

suite = testsuite("ExampleTest");

プラグインなしでテスト ランナーを作成します。次のコードで、出力を生成しないサイレント ランナーを作成します。

runner = testrunner("minimal");

次に、任意のプラグインを選択して追加できます。AssertionCountingPlugin クラスのインスタンスをランナーに追加して、テストを並列で実行します。(ランナーで run メソッドを呼び出すと、同じテストを逐次モードで実行することもできます。)

runner.addPlugin(AssertionCountingPlugin)
runner.runInParallel(suite);
## Running a total of 300 tests

Split tests into 18 groups and running them on 6 workers.
----------------
Finished Group 1
----------------
### Running a total of 20 tests in group 1

----------------
Finished Group 2
----------------
### Running a total of 20 tests in group 2

----------------
Finished Group 3
----------------
### Running a total of 19 tests in group 3

----------------
Finished Group 4
----------------
### Running a total of 19 tests in group 4

----------------
Finished Group 5
----------------
### Running a total of 18 tests in group 5

----------------
Finished Group 6
----------------
### Running a total of 18 tests in group 6

----------------
Finished Group 7
----------------
### Running a total of 18 tests in group 7

----------------
Finished Group 8
----------------
### Running a total of 17 tests in group 8

----------------
Finished Group 9
----------------
### Running a total of 17 tests in group 9

-----------------
Finished Group 10
-----------------
### Running a total of 17 tests in group 10

-----------------
Finished Group 11
-----------------
### Running a total of 16 tests in group 11

-----------------
Finished Group 12
-----------------
### Running a total of 16 tests in group 12

-----------------
Finished Group 13
-----------------
### Running a total of 15 tests in group 13

-----------------
Finished Group 14
-----------------
### Running a total of 15 tests in group 14

-----------------
Finished Group 16
-----------------
### Running a total of 14 tests in group 16

-----------------
Finished Group 15
-----------------
### Running a total of 15 tests in group 15

-----------------
Finished Group 17
-----------------
### Running a total of 14 tests in group 17

-----------------
Finished Group 18
-----------------
### Running a total of 12 tests in group 18

## Done running tests
______________________________
Total Assertions: 100
	88 Passed, 12 Failed

参考

関数

クラス

トピック