在inputParser章节中,我们通过不断改进getArea函数对输入参数的处理方法,引入这样一个观点:一个可靠的科学工程计算项目必须有一套测试系统,才能防止开发的过程中算法退化,工程项目的推进必须在算法开发和算法测试之间不断迭代完。在inputParser章节的最后,还根据直觉提出了一个测试系统所应该有的基本功能。在本章中,我们将学习MATLAB提供的测试解决方案:MATLAB单元测试(MATLAB Unit Test)。
基于函数的(Function-Based)单元测试的构造
MATLAB基于函数的单元测试构造很简单,如图1所示:用户通过一个主测试函数和若干局部测试函数(也叫做测试点,Local Function)来组织各个测试。而测试的运行则交给MATLAB的单元测试架构(以下简称Framework)去完成。
图1 单元测试Framework和测试函数
主测试函数和局部测试函数看上去和普通的MATLAB函数没有区别,其结构如图2所示,只是命名上有一些规定而已,这些特殊的规定是为了Framework可以和测试函数契合而规定的。
图2 简单的主测试函数和若干局部的测试函数构成的一个单元测试
命名规则如下:
主函数的名称由用户任意指定,和其他的MATLAB函数文件一样,该文件的名称需要和函数的名称的相同(如果主函数的名称是testmainfunc,该文件名称则是testmainfunc.m)
在主函数中,必须调用一个叫做functiontests的函数,搜集该函数中的所有局部函数,产生一个包含这些局部函数的函数局部的测试矩阵并返回给Framework
如下所示:
其中localfunctions是一个MATLAB函数,用来返回所有局部函数的函数句柄。局部函数的命名必须以test开头,局部函数只接受一个输入参数,即测试对象,即下面例子中的形参testCase:
其中testCase由单元测试Framework提供,即Framework将自动的调用该函数,并且提供testCase参数。按照规定,要运行单元测试中的所有测试,必须调用runtests函数:
下面用我们用基于函数的单元测试来给getArea函数的构造其单元测试。
getArea函数的单元测试:版本 I
首先给主测试文件起个名字叫做testGetArea,该名字是任意的,为了便于理解名字里面通常包含test,并包含要测试的主要函数的名字:
在该主函数中,localfunctions将搜集所有的局部函数,构造函数句柄数组并返回测试矩阵。这里自然会有一个问题,这个tests句柄数组将返回给谁,这就要了解Framework是如何和测试相互作用的。如图3所示,整个测试从runtests('testmainfunc.m')命令开始, 命令函数,Framework将首先调用testGetArea的主函数,得到所有的局部函数的函数句柄,如空心箭头线段所示,然后Framework再负责调用每一个测试局部函数,并且把testCase当做参数提供给每个局部函数,如虚线线段所示。我们可以把Framework想象成一个流水线,用户只需要通过runtests('testmainfunc.m')把“testmainfunc.m”放到流水线上并且“打开开关”就可以了。它是MATLAB的类matlab.unittest.FunctionTestCase的对象。
图3 单元测试Framework和测试函数的相互作用
返回的testCase是类matlab.unittest.FunctionTestCase的对象,有很多成员验证方法可以提供给用户调用,我们的第一版的getArea函数如下, 要求函数接受两个参数,并且都是数值类型:
我们先给这个getArea写第一个测试点,确保测试getArea函数在接受两个参数的时候,能给出正确的答案:
我们给testGetArea.m添加一个局部函数叫做testTwoInputs,按照规定,该局部函数的名字要以test开头,后面的名字要能够尽量反应该测试点的实际测试的内容。verifyTrue是一个testCase对象所支持的方法,它用来验证其第一个参数,作为一个表达式,是否为真。verifyTrue的第二个参数接受字符串,在测试失败时提供诊断提示。一个很常见的问题是:getArea是一个极其简单的函数,内部的工作就是把两个输入相乘,在这里验证getArea(10,22) == 220真的有必要吗?请读者记住这个问题,它是理解单元测试的精要之一。下面我们来运行这个测试:
测试返回一个matlab.unittest.TestResult对象,其中包括运行测试的结果,不出意料我们的函数通过了这轮简单的测试。如果函数没有通过测试,比如我们故意要验证一个错误的结果:getArea(10,22) ==0。
Framework将给出详尽的错误报告, 其中Test Diagnostic栏目中报告的就是verifyTrue函数中的第二个参数所提供的诊断信息。
我们再添加一个负面测试,回忆第一版的函数getArea不支持单个参数,如下:
我们可以利用lasterr函数得到了这个错误的Error ID,这个Error ID将在负面测试中用到。下面是这个负面测试,验证在只有一个输入的情况下,getArea函数能够如预期报错。我们给测试添加一个新的测试点,叫做testTwoInputsInvalid。
在testTwoInputsInvalid中,我们使用了测试对象的verifyError成员函数,它的第一个参数是函数句柄,即要执行的语言(会出错的语句),第二个参数是要验证的MATLAB错误的Error ID, 就是我们前面用lasterr函数得到的信息。verifyError内部还有try和catch,可以运行函数句柄,捕捉到错误,并且把Error ID和第二个参数做比较。再举一个例子,我们先在getArea函数中规定所有的输入必须是数值类型,所以如果输入的是字符串,getArea将报错,先再命令行中实验一下,以便得到Error ID:
然后再把这个负面测试添加到testGetArea中去:
运行一遍,一个正面测试,一个负面测试都全部通过。
getArea函数的单元测试: 版本II & III
测试的准备和清理工作: Tests Fixtures
验证方法: Types of Qualification
测试方法论和以测试驱动开发(Test-Driven Development)
-
字符串
+关注
关注
1文章
584浏览量
20552 -
函数
+关注
关注
3文章
4338浏览量
62739
发布评论请先 登录
相关推荐
评论