主要内容

编写插件以将数据添加到测试结果

R2026b

此示例说明如何创建一个插件,它将数据添加到 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')

Bar graph depicting error versus experiment

另请参阅

| | | | | |

主题