为单元测试公开M文件子功能的最简单方法是什么?

时间:2011-01-11 22:38:04

标签: unit-testing matlab

我最近一直在修改将完整的连续测试集成到我的Matlab开发周期中,遇到了一个我不知道如何解决的问题。正如几乎所有用户都知道的那样,Matlab从M文件之外的任何函数的视图中隐藏了M文件中的子函数。玩具示例如下所示:

function [things] = myfunc(data)
  [stuff] = mysubfunc(data)
  things = mean(stuff);
end

我想对subfunc本身进行单元测试。这是AFAIK,不可能,因为我无法从任何外部功能调用它。

我目前正在使用Steve Eddins的Matlab xUnit,无法解决这个问题。简单的解决方案 - 将子函数拆分为自己的M文件 - 在实践中是不可接受的,因为我将拥有许多我想要测试的小函数,并且不希望使用单独的M文件污染我的文件系统。如果不为我想测试的每个函数创建新文件,我该怎么做才能编写和执行简单的单元测试?

4 个答案:

答案 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