罗兰谈MATLAB的艺术

将想法转化为MATLAB

请注意

罗兰谈MATLAB的艺术已存档,不会更新。

使用MATLAB单元测试基础设施评分作业

Steven Lord, Andy Campbell和David Hruska是MathWorks质量工程小组的成员,他们今天是嘉宾博客,介绍R2013a中的一个新功能,MATLAB单元测试基础设施。有几个提交的MATLAB中央文件交换相关的单元测试MATLAB代码。博客作者Steve Eddins在2009年写了一个高度评价的例子。在R2013a版本中,MathWorks包含在MATLAB本身MATLAB行业标准xUnit测试框架的实现。

如果您不是软件开发人员,您可能想知道这个特性对您是否有用。在这篇文章中,我们将以教授批改学生作业为例,描述一种不认为自己是软件开发人员的人可以利用这个框架的方法。这并不是说听众中的开发人员应该继续下一篇文章;您可以使用这些工具来测试您自己的代码,就像教授可以使用它们来测试他或她的学生编写的代码一样。

这个特性中有大量的功能,我们将不在这里展示。欲了解更多信息,请参阅MATLAB单元测试框架文档。

内容

背景

为了使用此特性,您应该了解如何定义simpleMATLABclassdef文件,如何定义从另一个类继承的类,以及如何为这些类的方法和属性指定属性。的面向对象编程文档描述了这些功能。

问题陈述

作为编程入门课上的教授,您希望学生编写一个计算斐波那契数的程序。你给学生的确切问题陈述是:

创建一个函数“fib”,它接受一个非负整数n,并返回第n个斐波那契数。斐波那契数由以下关系生成:
F(0) = 1f (1) = 1f (n) = F(n-1) + F(n-2) for整数n > 1
如果n不是一个非负整数,函数应该抛出一个错误。

基本单元测验

最基本的MATLAB单元测试是一个MATLABclassdef类文件,该文件继承自matlab.unittest.TestCase类。在这篇文章的其余部分,我们将为这个基本框架添加额外的部分来增加这个测试的能力,并将更改它的名称以反映它增加的功能。

dbtypebasicTest.m
1 classdef basicTest < matlab.unittest.TestCase 2 3结束
test = basicTest
test = basicTest,没有属性。

运行测试

要运行测试,我们可以简单地通过测验运行函数。有更高级的方法可以让一组测试更容易,但对于我们的目的(每次检查一个学生的答案),这就足够了。当你一次检查多个学生的答案时,你可以使用运行在一个循环。

basicTest实际上并不验证学生函数的输出,它不会花很长时间执行。

结果=运行(测试)
results = 0x0 TestResult数组,属性:Name Passed Failed Incomplete Duration总计:0 Passed, 0 Failed, 0 Incomplete. 0秒测试时间。

假设一个叫Thomas的学生提交了一个函数fib.m作为他的解决方案。Thomas的代码存储在名为托马斯。.为了设置测试以检查Thomas的答案,我们将保存他的代码的文件夹添加到路径中。

目录“托马斯”);dbtypefib.m
1函数y = fib(n) 2如果n <= 1 3 y = 14 else 5 y = fib(n-1)+fib(n-2);6结束

检验F(0) = 1

basicTest是一个有效的测试类,我们可以运行它,但它实际上不会对学生的测试文件执行任何验证。执行该验证的方法需要在方法具有该属性的块测试指定。

matlab.unittest.TestCase类包含验证方法,可用于测试学生文件返回的结果的各种质量。您可能最常使用的限定方法是verifyEqual方法,如果传递给它的两个值相等则通过,如果不相等则报告测试失败。

文档matlab.unittest.TestCase类列出了许多其他确认方法,您可以使用它们来执行其他类型的验证,包括测试结果的数据类型和大小;将字符串结果匹配到期望的字符串;测试给定的代码段是否抛出特定错误或发出特定警告;还有更多。

这个简单的测试建立在generalTest通过添加一个测试方法,检查学生的函数在输入0时被调用时是否返回值1。

dbtypesimpleTest.m
1 classdef simpleTest < matlab.unittest.TestCase 2 methods(Test) 3 function fibonacciOfZeroShouldBeOne(testCase) 4 %计算学生的函数n = 0 5 result = fib(0);6 testCase。verifyEqual(结果,1);7结束8结束9结束

托马斯的作业解满足这个基本条件。我们可以使用返回的结果运行显示通过测试的百分比。

results = run(simpleTest) percentPassed = 100 * nnz([results. passed]) / numel(results);disp ([num2str (percentPassed),“%通过。”]);
运行simpleTest。Done simpleTest __________ results = TestResult with properties: Name: 'simpleTest/fibonacciOfZeroShouldBeOne' Passed: 1 Failed: 0 Incomplete: 0 Duration: 0.0112总计:1 Passed, 0 Failed, 0 Incomplete. 0.011168秒测试时间。100%通过。

测试F(pi)是否抛出错误

现在我们已经有了一个基本的正测试,我们可以添加一个测试,检查学生函数在传递非整数值时的行为(比如N = PI)作为输入。作业指出,当使用非整数值调用时,学生的函数应该出错。由于赋值操作不需要抛出特定的错误,所以只要心房纤颤(π)抛出任何异常。

dbtypeerrorCaseTest.m
1 classdef errorCaseTest < matlab.unittest.TestCase 2 methods(Test) 3 function fibonacciOfZeroShouldBeOne(testCase) 4 %评估学生的函数n = 0 5 result = fib(0);6 testCase。verifyEqual(结果,1);7 end 8 function fibonacciOfNonintegerShouldError(testCase) 9 testCase. verifyerror (@()fib(pi), ?MException);10结束11结束12结束

Thomas忘记在他的函数中包含对非整数值输入的检查,所以我们的测试应该通过报告失败来表明这一点。

results = run(errorCaseTest) percentPassed = 100 * nnz([results. passed]) / numel(results);disp ([num2str (percentPassed),“%通过。”]);
运行errorCaseTest。================================================================================ 验证失败errorCaseTest / fibonacciOfNonintegerShouldError。--------------------- 框架的诊断 : --------------------- verifyError失败了。——>函数没有抛出任何异常。预期异常类型:MException评估函数:@()fib(pi) ------------------堆栈信息:------------------在C:\Program Files\MATLAB\R2013a\toolbox\matlab\ testframework\+matlab\+unittest\+资格\可验证。m (verified . verifyerror) at 637 In H:\Documents\LOREN\MyJob\Art of MATLAB\errorCaseTest。在9米(errorCaseTest.fibonacciOfNonintegerShouldError)  ================================================================================ .完成errorCaseTest  __________ 失败失败总结:名字不完整的原因(s ) ============================================================================================= errorCaseTest / fibonacciOfNonintegerShouldError X验证失败。results = 1x2 TestResult数组,属性:Name Passed Failed不完全持续时间总计:1 Passed, 1 Failed, 0 Incomplete 0.026224秒测试时间。50%通过。

另一个学生Benjamin在他的代码中检查了一个非整数值,您可以在第2行中看到。

rmpath (“托马斯”);目录“本杰明”);dbtypefib.m
1函数y = fib(n) 2 if (n ~= round(n)) || n < 0 3错误(' n不是整数!');4 elseif n == 0 || n == 1 5 y = 1;6 else 7 y = fib(n-1)+fib(n-2);8日结束

Benjamin的代码通过了fibonacciOfZeroShouldBeOne方法(我们复制到errorCaseTestsimpleTest的新测试用例实现fibonacciOfNonintegerShouldError方法。

results = run(errorCaseTest) percentPassed = 100 * nnz([results. passed]) / numel(results);disp ([num2str (percentPassed),“%通过。”]);
运行errorCaseTest ..Done errorCaseTest __________ results = 1x2 TestResult数组,属性:Name Passed Failed Incomplete Duration总计:2 Passed, 0 Failed, 0 Incomplete 0.010132秒测试时间。100%通过。

学生基本测试,教师高级测试

这篇文章前面给出的问题陈述是我们布置给学生的家庭作业的纯文本描述。我们还可以通过给学生一个他们可以运行的测试文件,在代码中(如果他们使用的是R2013a或更高版本)为他们陈述问题simpleTesterrorCaseTest.他们可以直接使用这个“需求测试”来确保他们的功能满足任务的需求。

dbtypestudentTest.m
1 classdef studentTest < matlab.unittest.TestCase 2 methods(Test) 3 function fibonacciOfZeroShouldBeOne(testCase) 4 %评估学生的函数n = 0 5 result = fib(0);6 testCase。verifyEqual(结果,1);7 end 8 function fibonacciOfNonintegerShouldError(testCase) 9 testCase. verifyerror (@()fib(pi), ?MException);10结束11结束12结束

为了让学生的代码通过作业,它需要通过中给出的测试用例学生单元测试。但是,我们不想用学生随着只有检查学生的密码。如果我们这样做了,学生可以编写他们的函数来只覆盖学生测试文件中的测试用例。

我们可以通过拥有两个独立的测试文件来解决这个问题,一个包含学生测试用例,另一个包含教师在评分过程中使用的额外测试用例。我们能否避免手动运行两个测试文件,或者在教师测试中复制学生测试用例的代码?是的!

为此,我们编写了一个教员测试文件,通过继承来合并学生测试文件。然后,我们可以向指导者测试文件添加额外的测试用例。当我们运行这个测试时,它应该运行三个测试用例;两个继承自学生fibonacciOfZeroShouldBeOne而且fibonacciOfNonintegerShouldError,一个来自instructorTest本身,fibonacciOf5

dbtypeinstructorTest.m
因为student测试文件是一个matlab.unittest.TestCase,并且3% instructorTest继承自它,instructorTest也是一个4%的matlab.unittest.TestCase。5 6 method (Test) 7 function fibonacciOf5(testCase) 8 %评估学生的函数n = 5 9 result = fib(5);10 testCase。verifyEqual(result, 8, 'Fibonacci(5) should be 8'); 11 end 12 end 13 end

让我们看一下Eric通过的测试文件studentTestFile测试,但在其中他完全忘记了实现F(n) = F(n-1)+F(n-2)递归步骤。

rmpath (“本杰明”);目录“埃里克。”);dbtypefib.m
1函数y = fib(n) 2 if (n ~= round(n)) || n < 0 3错误(' n不是整数!');4结束5 y = 1;

它应该通过学生单元测试。

结果= run(studentTest);percentPassed = 100 * nnz([results. passed]) / numel(results);disp ([num2str (percentPassed),“%通过。”]);
运行studentTest ..完成studentTest __________ 100%通过。

它没有通过指导单元测试,因为它失败了一个测试用例。

results = run(instructorTest) percentPassed = 100 * nnz([results. passed]) / nummel (results);disp ([num2str (percentPassed),“%通过。”]);
跑步教练测试..================================================================================ 验证失败instructorTest / fibonacciOf5。---------------- 测试诊断 : ---------------- 斐波纳契(5)应该是8  --------------------- 框架的诊断 : --------------------- verifyEqual失败了。——> NumericComparator失败。——>使用"isequal "两个值不相等。实际值:1期望值:8 ------------------堆栈信息:------------------在C:\Program Files\MATLAB\R2013a\toolbox\matlab\ testframework\+matlab\+unittest\+qualification \可验证。m (verified . verifyequal) at 411 In H:\Documents\LOREN\MyJob\Art of MATLAB\instructorTest。在10米(instructorTest.fibonacciOf5)  ================================================================================ .完成instructorTest  __________ 失败失败总结:名字不完整的原因(s ) ========================================================================== instructorTest / fibonacciOf5 X验证失败。results = 1x3 TestResult数组,属性:Name Passed Failed不完全持续时间总计:2 Passed, 1 Failed, 0 Incomplete 0.028906秒测试时间。66.6667%通过。

我们在上面测试了Benjamin的代码,他为作业问题编写了正确的解决方案。

rmpath (“埃里克。”);目录“本杰明”);results = run(instructorTest) percentPassed = 100 * nnz([results. passed]) / nummel (results);disp ([num2str (percentPassed),“%通过。”]);rmpath (“本杰明”);
跑步教练测试…Done instructorTest __________ results = 1x3带有属性的TestResult数组:名称通过失败不完整持续时间总计:3通过,0失败,0不完整0.015946秒测试时间。100%通过。

结论

在这篇文章中,我们向您展示了使用新的MATLAB单元测试基础设施的基础知识,使用作业评分作为用例。

我们检查了学生的代码是否对一个有效值有效(通过返回正确答案),对一个无效值有效(通过抛出错误)。我们还展示了如何使用此基础设施为学生提供帮助/检查,您也可以将其用作评分的一部分。

我们希望对单元测试框架的简要介绍已经向您展示了如何使用这个特性,即使您不认为自己是软件开发人员。请在这篇文章的评论中告诉我们你将如何使用这个新功能。或者,如果你已经试过用matlab。Unittest,让我们知道你的经历在这里

MATLAB®R2013a发布

|
  • 打印
  • 发送电子邮件