开发者

What is the easiest way to expose M-file subfunctions for unit testing?

开发者 https://www.devze.com 2023-02-04 05:30 出处:网络
I have been tinkering lately with fully integrating continuous testing into my Matlab development cycle and have run across a problem I don\'t know how to get around. As almost all users know, Matlab

I have been tinkering lately with fully integrating continuous testing into my Matlab development cycle and have run across a problem I don't know how to get around. As almost all users know, Matlab kindly hides sub-functions within an M-file from the view of any functions outside that M-file. A toy example can be seen below:

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

I want to perform unit testing on subfunc itself. Thi开发者_如何转开发s is, AFAIK, impossible because I cannot call it from any external function.

I'm currently using Matlab xUnit by Steve Eddins and cannot get around this issue. The easy solution -- splitting subfunc out to its own M-file -- is not acceptable in practice because I will have numerous small functions I want to test and don't want to pollute my filesystem with a separate M-file for each one. What can I do to write and perform easy unit tests without making new files for each function I want to test?


What you need to do in general is get function handles to your subfunctions from within the primary function and pass them outside the function where you can unit test them. One way to do this is to modify your primary function such that, given a particular set of input arguments (i.e. no inputs, some flag value for an argument, etc.), it will return the function handles you need.

For example, you can add a few lines of code to the beginning of your function so that it returns all of the subfunction handles when no input is specified:

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

Or, if you prefer specifying an input flag (to avoid any confusion associated with accidentally calling the function with no inputs as Jonas mentions in his comment), you could return the subfunction handles when the input argument data is a particular character string. For example, you could change the input checking logic in the above code to this:

if ischar(data) && strcmp(data, '-getSubHandles')


I have a pretty hacky way to do this. Not perfect but at least it's possible.

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

Then at the prompt this will do the trick:

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

>> 


Have you used the new-style classes? You could turn that function in to a static method on a utility class. Then you could either turn the subfunctions in to other static methods, or turn the subfunctions in to local functions to the class, and give the class a static method that returns the handles to them.

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

If you use the classdef style syntax, all these functions, and any other methods, can all go in a single fooUtil.m file; no filesystem clutter. Or, instead of exposing the private stuff, you could write the test code inside the class.

I think the unit testing purists will say you shouldn't be doing this at all, because you should be testing against the public interface of an object, and if you need to test the subparts they should be factored out to something else that presents them in its public interface. This argues in favor of making them all public static methods and testing directly against them, forgetting about exposing private functions with function handles.

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            


I use a method that mirrors the way GUIDE use to generate its entry methods. Granted it's biased towards GUIs...

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

This allows you to do the following

% Calls bar in foo passing 10 and 1
foo('bar', 10, 1)

0

精彩评论

暂无评论...
验证码 换一张
取 消