2011-01-11 24 views
12

He estado retocando últimamente con la integración completa de pruebas continuas en mi ciclo de desarrollo Matlab y he encontrado un problema que no sé cómo manejar. Como casi todos los usuarios saben, Matlab oculta gentilmente las subfunciones dentro de un archivo M desde la vista de cualquier función fuera de ese archivo-M. Un ejemplo de juguete se puede ver a continuación:¿Cuál es la forma más fácil de exponer subfunciones de archivos M para pruebas unitarias?

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

Quiero realizar pruebas unitarias en subfunc. Esto es, AFAIK, imposible porque no puedo llamarlo desde ninguna función externa.

Actualmente estoy usando Matlab xUnit por Steve Eddins y no puedo evitar este problema. La solución fácil - dividir el subfunc en su propio archivo M - no es aceptable en la práctica porque tendré numerosas funciones pequeñas que quiero probar y no quiero contaminar mi sistema de archivos con un archivo M separado para cada uno . ¿Qué puedo hacer para escribir y realizar pruebas sencillas de unidades sin hacer nuevos archivos para cada función que quiero probar?

Respuesta

1

Tengo una forma bastante hacky de hacer esto. No es perfecto, pero al menos es posible.

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 

A continuación, en el indicador de esto va a hacer el truco:

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

>> 
+0

Es decir, no estoy exponiendo los subfuncs en absoluto :) – William

12

Lo que hay que hacer en general es conseguir function handles a sus subfunciones dentro de la función primaria y pasarlos fuera de la función donde puede probarlos por unidad Una forma de hacerlo es modificar su función principal de forma tal que, dado un conjunto particular de argumentos de entrada (es decir, sin entradas, algún valor de indicador para un argumento, etc.), devolverá los identificadores de función que necesite.

Por ejemplo, puede agregar unas pocas líneas de código al principio de su función para que devuelva toda la subfunción maneja cuando no se especifica de entrada:

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 

O, si lo prefiere especificar una indicador de entrada (para evitar cualquier confusión asociada con accidentalmente llamando a la función sin entradas como Jonas menciona en su comentario), puede devolver los identificadores de subfunción cuando el argumento de entrada data es una cadena de caracteres en particular. Por ejemplo, puede cambiar la lógica de verificación de entrada en el código anterior a esto:

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

1 Yo diría que esta es la mejor manera de hacerlo (una bandera de entrada que activará el modo de prueba [como un oculto/característica no documentada]) ... – Amro

+3

+1 - aunque crearía los identificadores de función usando un argumento de entrada 'returnSubfunctionHandles'. Llamar a una función que de otra manera requeriría entrada con cero argumentos de entrada es un error que puede ocurrir ocasionalmente, y puede llevar un tiempo rastrear de dónde provienen esos mangos extraños. – Jonas

+0

Encontré esta respuesta muy útil y publiqué una pregunta relacionada sobre cómo generar la lista de subfunciones que se devolverán automáticamente. En 2013b en adelante hay una manera fácil de hacerlo usando 'localfunctions': http://stackoverflow.com/questions/37508773/list-subfunctions-defined-in-function-file-within-calling-environment – Alex

1

Utilizo un método que refleja la forma en que GUIDE utiliza para generar sus métodos de entrada. Pero reconozco que está sesgado hacia interfaces gráficas de usuario ...

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 

Esto le permite hacer llamadas del poste tras

% en foo paso 10 y 1
foo('bar', 10, 1)

1

Han usaste las clases de nuevo estilo? Puede convertir esa función en un método estático en una clase de utilidad. Luego, puede convertir las funciones secundarias en otros métodos estáticos, o convertir las funciones secundarias en funciones locales para la clase, y otorgar a la clase un método estático que les devuelva los identificadores.

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 

Si utiliza la sintaxis de estilo classdef, todas estas funciones, y cualquier otro método, puede ir todo en un solo archivo fooUtil.m; sin desorden en el sistema de archivos. O bien, en lugar de exponer las cosas privadas, podrías escribir el código de prueba dentro de la clase.

Creo que los puristas de las pruebas unitarias dirán que no deberías estar haciendo esto en absoluto, porque deberías probar contra la interfaz pública de un objeto, y si necesitas probar las subpartes deberían tener en cuenta algo más que los presenta en su interfaz pública. Esto argumenta a favor de hacer que todos los métodos estáticos públicos y las pruebas directamente en contra de ellos, olvidando la exposición de funciones privadas con identificadores de función.

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