Unit testing 为单元测试公开M文件子功能的最简单方法是什么?
我最近一直在修修补补将连续测试完全集成到我的Matlab开发周期中,遇到了一个我不知道如何解决的问题。几乎所有用户都知道,Matlab很友好地将M文件中的子函数隐藏在该M文件之外的任何函数的视图中。玩具示例如下所示:Unit testing 为单元测试公开M文件子功能的最简单方法是什么?,unit-testing,matlab,Unit Testing,Matlab,我最近一直在修修补补将连续测试完全集成到我的Matlab开发周期中,遇到了一个我不知道如何解决的问题。几乎所有用户都知道,Matlab很友好地将M文件中的子函数隐藏在该M文件之外的任何函数的视图中。玩具示例如下所示: function [things] = myfunc(data) [stuff] = mysubfunc(data) things = mean(stuff); end 我想在subfunc本身上执行单元测试。这是不可能的,因为我不能从任何外部函数调用它 我目前正在使用S
function [things] = myfunc(data)
[stuff] = mysubfunc(data)
things = mean(stuff);
end
我想在subfunc本身上执行单元测试。这是不可能的,因为我不能从任何外部函数调用它
我目前正在使用Steve Eddins的Matlab xUnit,无法回避这个问题。这种简单的解决方案——将子UNC拆分为它自己的M文件——在实践中是不可接受的,因为我将有许多小函数要测试,并且不希望每个函数都有一个单独的M文件来污染我的文件系统。如何编写和执行简单的单元测试,而不为每个要测试的函数创建新文件?我有一种非常简单的方法。不完美,但至少是可能的
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
>>
通常,您需要做的是从主函数内部访问子函数,并将它们传递到函数外部,以便对它们进行单元测试。一种方法是修改主函数,这样,给定一组特定的输入参数(即无输入、参数的某些标志值等),它将返回所需的函数句柄 例如,可以在函数开头添加几行代码,以便在未指定输入时返回所有子函数句柄:
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')
我使用了一种方法,它反映了GUIDE生成其输入方法的方式。就算它对GUI有偏见 富美 这允许您执行以下操作 %通过10和1调用foo中的bar
foo('bar',10,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
也就是说,我根本没有公开子函数:)我将使用一个特殊的输入参数来实现这一点,而不是使用一个全局变量。把这个答案和+1结合起来,我认为这是最好的方法(一个会触发测试模式的输入标志[像一个隐藏/未记录的特性])…+1-尽管我会使用
returnSubfunctionHandles
输入参数创建函数句柄。调用一个本来需要零输入参数输入的函数是一个偶尔会发生的错误,并且可能需要一段时间才能找到这些奇怪句柄的来源。我发现这个答案非常有用,并发布了一个相关问题,关于如何自动生成要返回的子函数列表。在2013b以后,有一种使用localfunctions
实现这一点的简单方法:我认为单元测试应该在待测试代码所在的位置编写。在这种情况下,myfunc-runTests
比myfunc-getSubHandles
更符合逻辑。
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