Каков самый простой способ выставить подфункции M файла для модульного тестирования?
В последнее время я возился с полной интеграцией непрерывного тестирования в мой цикл разработки Matlab и столкнулся с проблемой, о которой я не знаю, как обойти. Как известно почти всем пользователям, Matlab любезно скрывает подфункции в M файле с точки зрения любых функций вне этого M файла. Пример игрушки можно увидеть ниже:
function [things] = myfunc(data)
[stuff] = mysubfunc(data)
things = mean(stuff);
end
Я хочу выполнить модульное тестирование на самом субфонсе. Это AFAIK невозможно, потому что я не могу назвать это из любой внешней функции.
В настоящее время я использую Matlab xUnit от Steve Eddins и не могу обойти эту проблему. Простое решение - разделение подмножества на собственный M файл - на практике неприемлемо, потому что у меня будет множество небольших функций, которые я хочу протестировать, и не хочу загрязнять мою файловую систему отдельным M файлом для каждого из них, Что я могу сделать для написания и выполнения простых модульных тестов без создания новых файлов для каждой функции, которую я хочу проверить?
Ответы
Ответ 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