使用自定义插件并行运行测试
此示例说明如何创建一个支持并行运行测试的自定义插件。该自定义插件对测试套件的通过和失败断言数进行计数。为了扩展 TestRunner
,该插件会覆盖 matlab.unittest.plugins.TestRunnerPlugin
类的所选方法。此外,为了支持并行运行测试,该插件会创建 matlab.unittest.plugins.Parallelizable
接口的子类。要并行运行测试,您需要 Parallel Computing Toolbox™。
创建插件类
在当前文件夹中的文件中,创建可并行化的插件类 AssertionCountingPlugin
,它同时从继承自 TestRunnerPlugin
和 Parallelizable
类。有关 AssertionCountingPlugin
的完整代码,请参阅插件类定义总结。
要跟踪通过和失败的断言数,请在 properties
块中定义四个只读属性。当前并行池中的每个 MATLAB® 工作进程使用 NumPassingAssertions
和 NumFailingAssertions
来跟踪运行 TestSuite
数组的一部分时的通过和失败断言数。MATLAB 客户端使用 FinalizedNumPassingAssertions
和 FinalizedNumFailingAssertions
汇总来自不同工作进程的结果,并在测试会话结束时报告通过和失败断言的总数。
properties (SetAccess = private) NumPassingAssertions NumFailingAssertions FinalizedNumPassingAssertions FinalizedNumFailingAssertions end
扩展测试会话的运行
要扩展整个 TestSuite
数组的运行,请在具有 protected
访问权限的 methods
块中覆盖 TestRunnerPlugin
的 runSession
方法。测试框架在客户端对此方法进行一次计算。
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
runSession
显示关于 Test
元素总数的信息,初始化插件用于生成文本输出的属性,并调用超类方法来触发整个测试运行。在框架完成对超类方法的计算后,runSession
通过调用辅助方法 printAssertionSummary
(请参阅定义辅助方法)显示断言计数摘要。
扩展共享测试脚手架和 TestCase 实例的创建
将侦听程序添加到 AssertionPassed
和 AssertionFailed
事件以统计断言数。要添加这些侦听程序,请扩展测试框架用于创建测试内容的方法。测试内容包括每个 Test
元素的 TestCase
实例,TestClassSetup
和 TestClassTeardown
方法代码块的类级别 TestCase
实例,以及当 TestCase
类具有 SharedTestFixtures
属性时使用的 Fixture
实例。
在覆盖创建方法时调用相应的超类方法。这些创建方法返回测试框架针对其各自上下文创建的内容。当使用 incrementPassingAssertionsCount
和 incrementFailingAssertionsCount
辅助方法实现上述方法时,将插件所需的侦听程序添加到返回的 Fixture
或 TestCase
实例。
将这些创建方法添加到具有 protected
访问权限的 methods
代码块。
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
数组分成不同的组,并将它们分配给工作进程进行处理。每个工作进程可以运行一个或多个测试套件部分。要自定义工作进程的行为,请在具有 protected
访问权限的 methods
块中覆盖 TestRunnerPlugin
的 runTestSuite
方法。
扩展 TestRunner
以显示单个工作进程运行的每个测试组的标识符以及该组中 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
使用两个字段对断言计数进行分组。
扩展最终测试套件部分的报告
扩展 reportFinalizedSuite
以通过检索每个最终测试套件部分的测试数据来汇总断言计数。要检索为一个测试套件部分存储的 assertionStruct
,请在 reportFinalizedSuite
的范围内调用 retrieveFrom
方法。将字段值添加到对应的类属性,并调用超类方法。测试框架在客户端上计算此方法的次数等于测试套件部分的个数。
methods (Access = protected) function reportFinalizedSuite(plugin, pluginData) assertionStruct = plugin.retrieveFrom(pluginData.CommunicationBuffer); plugin.FinalizedNumPassingAssertions = ... plugin.FinalizedNumPassingAssertions + assertionStruct.Passing; plugin.FinalizedNumFailingAssertions = ... plugin.FinalizedNumFailingAssertions + assertionStruct.Failing; reportFinalizedSuite@matlab.unittest.plugins.TestRunnerPlugin(... plugin, pluginData); end end
定义辅助方法
在具有 private
访问权限的 methods
块中,定义三个辅助方法。这些方法累加每个运行测试套件部分中的通过或失败断言数,并输出断言计数摘要。
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); 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
的文件,其中包含以下参数化测试类。该类产生 300 个 Test
元素,其中 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
将插件添加到 TestRunner 并运行测试
在命令提示符下,基于 ExampleTest
类创建测试套件。
import matlab.unittest.TestSuite import matlab.unittest.TestRunner suite = TestSuite.fromClass(?ExampleTest);
创建一个没有插件的 TestRunner
实例。此代码创建一个静默运行程序,以便您控制安装的插件。
runner = TestRunner.withNoPlugins;
将 AssertionCountingPlugin
添加到运行程序并以并行方式运行测试。如果对运行程序调用 run
方法,您还可以在串行模式下运行相同的测试。
runner.addPlugin(AssertionCountingPlugin) result = runner.runInParallel(suite);
Starting parallel pool (parpool) using the 'local' profile ... Connected to the parallel pool (number of workers: 6). ## Running a total of 300 tests Split tests into 18 groups and running them on 6 workers. ---------------- Finished Group 6 ---------------- ### Running a total of 18 tests in group 6 ---------------- 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 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 15 ----------------- ### Running a total of 15 tests in group 15 ----------------- Finished Group 14 ----------------- ### Running a total of 15 tests in group 14 ----------------- Finished Group 17 ----------------- ### Running a total of 14 tests in group 17 ----------------- Finished Group 16 ----------------- ### Running a total of 14 tests in group 16 ----------------- Finished Group 13 ----------------- ### Running a total of 15 tests in group 13 ----------------- Finished Group 18 ----------------- ### Running a total of 12 tests in group 18 ## Done running tests ______________________________ Total Assertions: 100 88 Passed, 12 Failed
另请参阅
matlab.unittest.plugins.TestRunnerPlugin
| matlab.unittest.TestCase
| matlab.unittest.TestRunner
| matlab.unittest.fixtures.Fixture
| matlab.unittest.plugins.Parallelizable
| matlab.unittest.TestSuite
| addlistener
| runInParallel
| matlab.unittest.TestResult