如何使用MATLAB写测试(3)combinatorial explosion? 参数化!
原文:如何使用MATLAB写测试(3)combinatorial explosion? 参数化! - 知乎 (zhihu.com)
最近我们的俄罗斯实习生有些头大,他为foo程序写的正向测试只有single一种情况。
function out = foo(in)
validateattributes(in,{'numeric'},{'nonempty'});
% Returns zero
out = zeros(size(in),'like',in);
end
他的老板看到他的测试后问他,你知道MATLAB中数据类型有几种吗?俄罗斯实习生虽然没读过孔乙己的故事,但至少也知道有double, single, uint8几种常见的类型。于是他尝试更新了下他的测试。
classdef myTest < matlab.unittest.TestCase
methods (Test)
function testSingle(test) %function唯一的参数test是你的测试对象
% Verifies single input case
in = single(10); %输入
expOut = zeros(1,'single'); %期待的输出
actualOut = foo(in); %调用待测程序
test.verifyEqual(actualOut,expOut); %比较实际输出与期待输出
end
function testDouble(test)
% Verifies double input case
in = double(10); %输入
expOut = zeros(1,'double'); %期待的输出
actualOut = foo(in); %调用待测程序
test.verifyEqual(actualOut,expOut); %比较实际输出与期待输出
end
% Negative test case
function testEmptyError(test)
% Verifies error on empty input
in = [];
expErrorId = 'MATLAB:expectedNonempty';
%传入function handle, 给出期待的error id
test.verifyError(@()foo(in),expErrorId);
end
end
end
作为一个合格的实习生,他一下就发现了接下来会发生的事情。对于每一个数据类型,他都要写一个完全一样的测试。公司招他来可不是做ctrl+c,ctrl+v的。而且要是以后有更多的测试,每个都需要对不同数据类型做相同的test case, 那要多少啊?
还好我们的数学教育不差
他心想。手上迅速算着帐。
假设MATLAB有N种数据类型,而我为每个数据类型都需要写M个测试,假设M个测试对每个数据类型都是相同的,那我就会有
𝐶𝑁1∗𝑀=𝑁∗𝑀
个测试,这还是以我的程序需要一个输入为前提。
这是如果我的程序需要两个输入,比如foo(x,y)
那我要写的测试数量就变成了
𝐶𝑁1∗𝐶𝑁1∗𝑀=𝑁2∗𝑀
...
如果程序需要k个输入,那要写的测试数量就变成了
𝑁𝑘∗𝑀
想到这里他背后直冒冷汗,难道自己的一生就耗在写测试上了?
MATLAB这么高大上的语言一定不会允许这件事发生的。
他边这么想着边翻阅着文档,突然发现了Parametrized Tests这篇教程。他按部就班地更新了自己的测试
classdef myTest < matlab.unittest.TestCase
properties(TestParameter) %定义测试参数
type = {'double','single','uint8'}
end
methods (Test)
function testInput(test, type) %传入测试参数
% Verifies single input case
in = cast(10,type); %输入
expOut = zeros(1,type); %期待的输出
actualOut = foo(in); %调用待测程序
test.verifyEqual(actualOut,expOut); %比较实际输出与期待输出
end
end
end
跑test
>> result = runtests('myTest')
Running myTest
...
Done myTest
__________
result =
1x3 TestResult array with properties:
Name
Passed
Failed
Incomplete
Duration
Details
Totals:
3 Passed, 0 Failed, 0 Incomplete.
0.12308 seconds testing time.
注意到了吗,虽然只写了1个test case, 但是结果result中却有3个TestResult。展开看看究竟是什么?
>> result.Name
ans =
myTest/testInput(supportedClass=double)
ans =
myTest/testInput(supportedClass=single)
ans =
myTest/testInput(supportedClass=uint8)
哦!
掌握了基本参数化测试的俄罗斯实习生决定试试更高级的技巧,他为自己的源程序加入了一个新的输入
function out = foo(in1 , in2)
assert(isequal(size(in1),size(in2)),'input 1 and input 2 must be of same size'); % ugly but works
validateattributes(in1,{'numeric'},{'nonempty'});
validateattributes(in2,{'numeric'},{'nonempty'});
% Cast everything into input 1's class
in2 = cast(in2,class(in1));
% Returns zero
out = zeros(size(in1),'like',in1) * zeros(size(in2),'like',in2) ;
end
并更新了测试
classdef myTest < matlab.unittest.TestCase
properties(TestParameter) %定义测试参数
type1 = {'double','single','uint8'}
type2 = {'double','single','uint8'}
end
methods (Test)
function testInput(test, type1, type2) %传入测试参数
% Verifies single input case
in1 = cast(10,type1); %输入
in2 = cast(10,type2); %输入
expOut = zeros(1,type1); %期待的输出
actualOut = foo(in1,in2); %调用待测程序
test.verifyEqual(actualOut,expOut); %比较实际输出与期待输出
end
end
end
跑tests
>> result = runtests('myTest')
Running myTest
.........
Done myTest
__________
result =
1x9 TestResult array with properties:
Name
Passed
Failed
Incomplete
Duration
Details
Totals:
9 Passed, 0 Failed, 0 Incomplete.
0.10923 seconds testing time.
有9个测试了,心算下……
3个数据类型,2个输入,1个测试方法
𝑁=3,𝑘=2,𝑀=1,𝑁𝑘∗𝑀=32∗1=9
对哦!
这下combinatorial explosion的问题就给计算机去解决吧!我1000刀买来配Oculus Rift的机器跑这点test还是可以的……
有兴趣的玩家(读者 可以阅读如何利用ParameterCombination来减少测试的数量
-参考资料