编写插件以将数据添加到测试结果
此示例说明如何创建一个插件,它将数据添加到 TestResult 对象。该插件将断言中的实际值和预期值追加到 TestResult 对象的 Details 属性。为了扩展 TestRunner,该插件会覆盖 matlab.unittest.plugins.TestRunnerPlugin 类的所选方法。
创建插件类
在当前文件夹中的文件中,创建自定义插件类 DetailsRecordingPlugin,该类继承自 TestRunnerPlugin 类。有关 DetailsRecordingPlugin 的完整代码,请参阅 DetailsRecordingPlugin 类定义总结。
要在 TestResult 对象中存储实际值和预期值,请在 properties 代码块中定义两个常量属性 ActField 和 ExpField。将 ActField 的值设置为包含实际值的 Details 结构体的字段的名称。将 ExpField 的值设置为包含预期值的字段的名称。
properties (Constant, Access = private) ActField = 'ActualValue'; ExpField = 'ExpectedValue'; end
将字段添加到详细信息属性
要向属于测试会话的所有 TestResult 对象的 Details 属性添加新字段,请在访问权限为 protected 的 methods 代码块中覆盖 TestRunnerPlugin 的 runSession 方法。runSession 向 TestResult 对象的 Details 结构体添加两个空字段,并调用超类方法来触发整个测试运行。
methods (Access = protected) function runSession(plugin,pluginData) resultDetails = pluginData.ResultDetails; resultDetails.append(plugin.ActField,{}) resultDetails.append(plugin.ExpField,{}) runSession@matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData); end end
为了添加字段,runSession 的实现包含对 matlab.unittest.plugins.plugindata.ResultDetails 类的 append 方法的调用。每次调用都会向 Details 结构体中添加一个空字段。
扩展共享测试脚手架和 TestCase 实例的创建
通过扩展测试框架用于创建测试内容的方法,为 AssertionPassed 和 AssertionFailed 事件添加侦听程序。测试内容包括每个 Test 元素的 TestCase 实例,TestClassSetup 和 TestClassTeardown 方法代码块的类级别 TestCase 实例,以及当 TestCase 类具有 SharedTestFixtures 属性时使用的 Fixture 实例。
在覆盖创建方法时调用相应的超类方法。每当执行断言时,您添加到返回的 Fixture 或 TestCase 实例的侦听程序都会导致 reactToAssertion 辅助方法执行。要将断言数据添加到测试结果,请将结果修改器实例与断言事件侦听程序数据一起传递给辅助方法。
将这些创建方法添加到具有 protected 访问权限的 methods 代码块。
methods (Access = protected) function fixture = createSharedTestFixture(plugin, pluginData) fixture = createSharedTestFixture@... matlab.unittest.plugins.TestRunnerPlugin(plugin, pluginData); resultDetails = pluginData.ResultDetails; fixture.addlistener('AssertionPassed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); fixture.addlistener('AssertionFailed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); end function testCase = createTestClassInstance(plugin,pluginData) testCase = createTestClassInstance@... matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData); resultDetails = pluginData.ResultDetails; testCase.addlistener('AssertionPassed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); testCase.addlistener('AssertionFailed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); end function testCase = createTestMethodInstance(plugin,pluginData) testCase = createTestMethodInstance@... matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData); resultDetails = pluginData.ResultDetails; testCase.addlistener('AssertionPassed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); testCase.addlistener('AssertionFailed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); end end
定义辅助方法
在访问权限为 private 的 methods 代码块中,定义辅助方法 reactToAssertion。此方法使用 QualificationEventData 实例基于 IsEqualTo 约束提取断言中的实际值和预期值,将提取的值转换为元胞数组,并将元胞数组追加到对应的 TestResult 对象的字段中。
methods (Access = private) function reactToAssertion(plugin,evd,resultDetails) if ~isa(evd.Constraint,'matlab.unittest.constraints.IsEqualTo') return end resultDetails.append(plugin.ActField,{evd.ActualValue}) resultDetails.append(plugin.ExpField,{evd.Constraint.Expected}) end end
DetailsRecordingPlugin 类定义总结
以下代码提供 DetailsRecordingPlugin 的完整内容。
classdef DetailsRecordingPlugin < matlab.unittest.plugins.TestRunnerPlugin properties (Constant, Access = private) ActField = 'ActualValue'; ExpField = 'ExpectedValue'; end methods (Access = protected) function runSession(plugin,pluginData) resultDetails = pluginData.ResultDetails; resultDetails.append(plugin.ActField,{}) resultDetails.append(plugin.ExpField,{}) runSession@matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData); end function fixture = createSharedTestFixture(plugin, pluginData) fixture = createSharedTestFixture@... matlab.unittest.plugins.TestRunnerPlugin(plugin, pluginData); resultDetails = pluginData.ResultDetails; fixture.addlistener('AssertionPassed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); fixture.addlistener('AssertionFailed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); end function testCase = createTestClassInstance(plugin,pluginData) testCase = createTestClassInstance@... matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData); resultDetails = pluginData.ResultDetails; testCase.addlistener('AssertionPassed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); testCase.addlistener('AssertionFailed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); end function testCase = createTestMethodInstance(plugin,pluginData) testCase = createTestMethodInstance@... matlab.unittest.plugins.TestRunnerPlugin(plugin,pluginData); resultDetails = pluginData.ResultDetails; testCase.addlistener('AssertionPassed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); testCase.addlistener('AssertionFailed',... @(~,evd)plugin.reactToAssertion(evd,resultDetails)); end end methods (Access = private) function reactToAssertion(plugin,evd,resultDetails) if ~isa(evd.Constraint,'matlab.unittest.constraints.IsEqualTo') return end resultDetails.append(plugin.ActField,{evd.ActualValue}) resultDetails.append(plugin.ExpField,{evd.Constraint.Expected}) end end end
创建示例测试类
在您的当前文件夹中,创建一个名为 ExampleTest.m 的文件,其中包含以下参数化测试类。该类生成一个包含 25 个元素的测试套件,每个元素对应于使用不同随机数生成器种子进行的一次试验。在每次试验中,测试框架创建一个由正态分布随机数组成的 1×100 向量,并断言实际样本均值和预期样本均值之间的差的幅值在 0.1 以内。
classdef ExampleTest < matlab.unittest.TestCase properties SampleSize = 100; end properties (TestParameter) seed = num2cell(randi(10^6,1,25)); end methods(Test) function testMean(testCase,seed) import matlab.unittest.constraints.IsEqualTo import matlab.unittest.constraints.AbsoluteTolerance rng(seed) testCase.assertThat(mean(randn(1,testCase.SampleSize)),... IsEqualTo(0,'Within',AbsoluteTolerance(0.1))); end end end
将插件添加到 TestRunner 并运行测试
在命令提示符下,基于 ExampleTest 类创建测试套件。
import matlab.unittest.TestSuite import matlab.unittest.TestRunner suite = TestSuite.fromClass(?ExampleTest);
创建一个没有插件的 TestRunner 实例。此代码创建一个静默运行器,以便您控制安装的插件。
runner = TestRunner.withNoPlugins;
将 DetailsRecordingPlugin 添加到运行器并运行测试。
runner.addPlugin(DetailsRecordingPlugin) result = runner.run(suite)
result =
1×25 TestResult array with properties:
Name
Passed
Failed
Incomplete
Duration
Details
Totals:
18 Passed, 7 Failed (rerun), 7 Incomplete.
0.12529 seconds testing time.
要检索有关随机数生成行为的详细信息,请根据测试结果的 Details 结构体创建一个结构体数组。
details = [result.Details]
details =
1×25 struct array with fields:
ActualValue
ExpectedValue
创建一个包含每个测试中实际值和预期值之间差的数组,然后在条形图中显示误差值。长度大于 0.1 的七个条形对应于失败的测试。
errorInMean = cell2mat([details.ExpectedValue]) - cell2mat([details.ActualValue]); bar(errorInMean) xlabel('Experiment') ylabel('Error')

另请参阅
matlab.unittest.plugins.TestRunnerPlugin | matlab.unittest.TestRunner | matlab.unittest.fixtures.Fixture | matlab.unittest.TestSuite | addlistener | matlab.unittest.TestResult | matlab.unittest.plugins.plugindata.ResultDetails