Main Content

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

此示例说明如何创建一个插件,它将数据添加到 TestResult 对象。该插件将断言中的实际值和预期值追加到 TestResult 对象的 Details 属性。为了扩展 TestRunner,该插件会覆盖 matlab.unittest.plugins.TestRunnerPlugin 类的所选方法。

创建插件类

在当前文件夹中的文件中,创建自定义插件类 DetailsRecordingPlugin,该类继承自 TestRunnerPlugin 类。有关 DetailsRecordingPlugin 的完整代码,请参阅 DetailsRecordingPlugin 类定义总结

要在 TestResult 对象中存储实际值和预期值,请在 properties 代码块中定义两个常量属性 ActFieldExpField。将 ActField 的值设置为包含实际值的 Details 结构体的字段的名称。将 ExpField 的值设置为包含预期值的字段的名称。

    properties (Constant, Access = private)
        ActField = 'ActualValue';
        ExpField = 'ExpectedValue';
    end

将字段添加到详细信息属性

要向属于测试会话的所有 TestResult 对象的 Details 属性添加新字段,请在访问权限为 protectedmethods 代码块中覆盖 TestRunnerPluginrunSession 方法。runSessionTestResult 对象的 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 实例的创建

通过扩展测试框架用于创建测试内容的方法,为 AssertionPassedAssertionFailed 事件添加侦听程序。测试内容包括每个 Test 元素的 TestCase 实例,TestClassSetupTestClassTeardown 方法代码块的类级别 TestCase 实例,以及当 TestCase 类具有 SharedTestFixtures 属性时使用的 Fixture 实例。

在覆盖创建方法时调用相应的超类方法。每当执行断言时,您添加到返回的 FixtureTestCase 实例的侦听程序都会导致 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

定义辅助方法

在访问权限为 privatemethods 代码块中,定义辅助方法 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

另请参阅

| | | | | |

相关主题