Какой самый простой способ выставить подфункции M-файла для модульного тестирования? - PullRequest
14 голосов
/ 12 января 2011

В последнее время я возился с полной интеграцией непрерывного тестирования в мой цикл разработки Matlab и столкнулся с проблемой, которую я не знаю, как обойти. Как знают почти все пользователи, Matlab любезно скрывает подфункции в M-файле от представления любых функций вне этого M-файла. Пример с игрушкой можно увидеть ниже:

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

Я хочу выполнить модульное тестирование на самом подфункции. Это, AFAIK, невозможно, потому что я не могу вызвать его из какой-либо внешней функции.

В настоящее время я использую Matlab xUnit от Стива Эддинса и не могу обойти эту проблему. Простое решение - разделение subfunc на его собственный M-файл - на практике неприемлемо, потому что у меня будет множество небольших функций, которые я хочу протестировать и не хочу загрязнять мою файловую систему отдельным M-файлом для каждого , Что я могу сделать, чтобы написать и выполнить простые модульные тесты, не создавая новые файлы для каждой функции, которую я хочу протестировать?

Ответы [ 4 ]

14 голосов
/ 12 января 2011

В общем, что вам нужно сделать, это получить функциональные дескрипторы для ваших подфункций из основной функции и передать их за пределы функции, где вы можете выполнить их модульное тестирование.Один из способов сделать это - изменить вашу основную функцию так, чтобы при заданном наборе входных аргументов (т. Е. Без входных данных, некотором значении флага для аргумента и т. Д.) Он возвращал нужные вам дескрипторы функций.

Например, вы можете добавить несколько строк кода в начало вашей функции, чтобы она возвращала все дескрипторы подфункции, когда ввод не указан:

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

Или, если вы предпочитаете указыватьфлаг ввода (чтобы избежать путаницы, связанной с случайным вызовом функции без ввода, как упоминает Джонас в своем комментарии), вы можете вернуть дескрипторы подфункции, когда входной аргумент data представляет собой определенную строку символов.Например, вы могли бы изменить логику проверки ввода в приведенном выше коде следующим образом:

if ischar(data) && strcmp(data, '-getSubHandles')
2 голосов
/ 12 января 2011

У меня есть довольно хакерский способ сделать это.Не идеально, но, по крайней мере, это возможно.

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

>> 
1 голос
/ 12 января 2011

Вы использовали классы нового стиля? Вы можете превратить эту функцию в статический метод в служебном классе. Затем вы можете либо превратить подфункции в другие статические методы, либо превратить подфункции в локальные функции класса и дать классу статический метод, который возвращает им дескрипторы.

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 голос
/ 12 января 2011

Я использую метод, который отражает способ использования GUIDE для генерации методов ввода. Разумеется, он смещен в сторону графического интерфейса ...

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)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...