Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

>> 

Ответ 3

Я использую метод, который отражает способ использования 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)

Ответ 4

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

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