我最近一直在修改将完整的连续测试集成到我的Matlab开发周期中,遇到了一个我不知道如何解决的问题。正如几乎所有用户都知道的那样,Matlab从M文件之外的任何函数的视图中隐藏了M文件中的子函数。玩具示例如下所示:
function [things] = myfunc(data)
[stuff] = mysubfunc(data)
things = mean(stuff);
end
我想对subfunc本身进行单元测试。这是AFAIK,不可能,因为我无法从任何外部功能调用它。
我目前正在使用Steve Eddins的Matlab xUnit,无法解决这个问题。简单的解决方案 - 将子函数拆分为自己的M文件 - 在实践中是不可接受的,因为我将拥有许多我想要测试的小函数,并且不希望使用单独的M文件污染我的文件系统。如果不为我想测试的每个函数创建新文件,我该怎么做才能编写和执行简单的单元测试?
答案 0 :(得分:14)
一般来说,您需要从主要功能中获取function handles到子功能,并将它们传递到可以对其进行单元测试的功能之外。一种方法是修改你的主函数,这样,给定一组特定的输入参数(即没有输入,参数的某些标志值等),它将返回你需要的函数句柄。
例如,您可以在函数的开头添加几行代码,以便在未指定输入时返回所有子函数句柄:
function things = myfunc(data)
if nargin == 0 % If data is not specified...
things = {@mysubfunc @myothersubfunc}; % Return a cell array of
% function handles
return % Return from the function
end
% The normal processing for myfunc...
stuff = mysubfunc(data);
things = mean(stuff);
end
function mysubfunc
% One subfunction
end
function myothersubfunc
% Another subfunction
end
或者,如果你更喜欢指定一个输入标志(为了避免与意外地调用没有输入的函数相关的混淆,如Jonas在他的评论中提到的那样),你可以在输入时返回子函数句柄参数data
是一个特定的字符串。例如,您可以将上面代码中的输入检查逻辑更改为:
if ischar(data) && strcmp(data, '-getSubHandles')
答案 1 :(得分:2)
我有一个非常黑客的方法来做到这一点。不完美,但至少有可能。
function [things] = myfunc(data)
global TESTING
if TESTING == 1
unittests()
else
[stuff] = mysubfunc(data);
things = mean(stuff);
end
end
function unittests()
%%Test one
tdata = 1;
assert(mysubfunc(tdata) == 3)
end
function [stuff] = mysubfunc(data)
stuff = data + 1;
end
然后在提示符下,这将解决问题:
>> global TESTING; TESTING = 1; myfunc(1)
??? Error using ==> myfunc>unittests at 19
Assertion failed.
Error in ==> myfunc at 6
unittests()
>> TESTING = 0; myfunc(1)
ans =
2
>>
答案 2 :(得分:1)
我使用的方法反映了GUIDE用于生成其输入方法的方式。当然,它偏向于GUI ...
Foo.m
function varargout=foo(varargin)
if nargin > 1 && ischar(varargin{1}) && ~strncmp( varargin{1},'--',2)
if nargout > 0
varargout = feval( varargin{:} );
else
feval = ( varargout{:} );
else
init();
end
这允许您执行以下操作
%在foo中调用条形码10和1
foo('bar', 10, 1)
答案 3 :(得分:1)
您是否使用过新式课程?您可以将该函数转换为实用程序类上的静态方法。然后你可以将子函数转换为其他静态方法,或者将子函数转换为本地函数到类中,并为类提供一个返回句柄的静态方法。
classdef fooUtil
methods (Static)
function [things] = myfunc(data)
[stuff] = mysubfunc(data);
things = mean(stuff);
end
function out = getLocalFunctionHandlesForTesting()
onlyAllowThisInsideUnitTest();
out.mysubfunc = @mysubfunc;
out.sub2 = @sub2;
end
end
end
% Functions local to the class
function out = mysubfunc(x)
out = x .* 2; % example dummy logic
end
function sub2()
% ...
end
function onlyAllowThisInsideUnitTest()
%ONLYALLOWTHISINSIDEUNITTEST Make sure prod code does not depend on this encapsulation-breaking feature
isUnitTestRunning = true; % This should actually be some call to xUnit to find out if a test is active
assert(isUnitTestRunning, 'private function handles can only be grabbed for unit testing');
end
如果使用classdef样式语法,所有这些函数和任何其他方法都可以放在一个fooUtil.m文件中;没有文件系统混乱。或者,您可以在类中编写测试代码,而不是公开私有内容。
我认为单元测试纯粹主义者会说你根本不应该这样做,因为你应该测试一个对象的公共接口,如果你需要测试子部分,他们应该被考虑到其他东西将它们呈现在公共界面中。这支持使它们成为所有公共静态方法并直接针对它们进行测试,而忽略了使用函数句柄公开私有函数。
classdef fooUtil
methods (Static)
function [things] = myfunc(data)
[stuff] = fooUtil.mysubfunc(data);
things = mean(stuff);
end
function out = mysubfunc(x)
out = x .* 2; % example dummy logic
end
function sub2()
% ...
end
end
end