2011-01-11 19 views
12

Ultimamente mi sono messo a fare i conti con la completa integrazione dei test continui nel mio ciclo di sviluppo Matlab e ho incontrato un problema che non so come muoversi. Come quasi tutti gli utenti sanno, Matlab nasconde gentilmente le sotto-funzioni all'interno di un M-file dalla vista di qualsiasi funzione al di fuori del file M. Un esempio di giocattolo può essere visto sotto:Qual è il modo più semplice per esporre sottofunzioni del file M per il test dell'unità?

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

Voglio eseguire il test dell'unità sul subfunc stesso. Questo è, AFAIK, impossibile perché non posso chiamarlo da nessuna funzione esterna.

Attualmente sto usando Matlab xUnit di Steve Eddins e non riesco a risolvere questo problema. La soluzione semplice - suddividere il subfunc in un proprio file M - non è accettabile nella pratica perché avrò numerose piccole funzioni che voglio testare e non voglio inquinare il mio filesystem con un M-file separato per ognuno . Cosa posso fare per scrivere ed eseguire semplici test unitari senza creare nuovi file per ogni funzione che voglio testare?

risposta

1

Ho un modo abbastanza hacky per farlo. Non perfetto ma almeno è possibile.

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 

Poi al prompt questo farà il trucco:

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

vale a dire, non sto esponendo le subfuncs a tutti :) – William

12

Quello che dovete fare, in generale, è ottenere function handles ai vostri sottofunzioni dall'interno della funzione primaria e li passano al di fuori della funzione di cui si può unità testarli. Un modo per farlo è modificare la tua funzione primaria in modo tale che, dato un particolare insieme di argomenti di input (cioè nessun input, qualche valore di flag per un argomento, ecc.), Restituirà gli handle di funzione di cui hai bisogno.

Ad esempio, è possibile aggiungere alcune righe di codice all'inizio della funzione in modo che restituisca tutti i subfunction maniglie quando non viene specificato alcun ingresso:

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, se si preferisce specificare un flag di input (per evitare qualsiasi confusione associata a accidentalmente chiamando la funzione senza input come indicato da Jonas nel suo commento), è possibile restituire gli handle di sottofunzione quando l'argomento di input data è una stringa di caratteri particolare. Ad esempio, è possibile modificare la logica di controllo input nel codice sopra riportato a questo:

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

+1 direi che questo è il modo migliore per andare su di esso (una bandiera di ingresso che attiverà la modalità di test [come una nascosta/funzione non documentata]) ... – Amro

+3

+1 - anche se creerei la funzione handle utilizzando un argomento di input 'returnSubfunctionHandles'. Chiamare una funzione che altrimenti richiederebbe input con argomenti di input pari a zero è un errore che potrebbe accadere occasionalmente e potrebbe richiedere del tempo per rintracciare da dove provenivano quelle strane maniglie. – Jonas

+0

Ho trovato questa risposta molto utile e ho postato una domanda correlata su come generare automaticamente l'elenco delle sottofunzioni da restituire. Nel 2013 in poi c'è un modo semplice per farlo usando 'localfunctions': http://stackoverflow.com/questions/37508773/list-subfunctions-defined-in-function-file-within-calling-environment – Alex

1

Io uso un metodo che rispecchia il modo in cui GUIDE utilizza per generare i suoi metodi di immissione. Concesso è sbilanciata verso le 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 

Questo vi permette di effettuare le seguenti operazioni

% bloccare le chiamate in foo passaggio 10 e 1
foo('bar', 10, 1)

1

Hanno hai usato le classi di nuovo stile? È possibile trasformare quella funzione in un metodo statico su una classe di utilità. Quindi è possibile trasformare le sottofunzioni in altri metodi statici oppure trasformare le funzioni secondarie in funzioni locali nella classe e assegnare alla classe un metodo statico che restituisca loro gli handle.

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 

Se si utilizza la sintassi stile classdef, tutte queste funzioni, e altri metodi, possono andare tutti in un unico file fooUtil.m; nessun disordine del filesystem. Oppure, invece di esporre le cose private, potresti scrivere il codice di test all'interno della classe.

Penso che i puristi dell'unità di test diranno che non dovresti farlo affatto, perché dovresti provare contro l'interfaccia pubblica di un oggetto e se hai bisogno di testare le sottoparti dovrebbero essere prese in considerazione per qualcosa altro che li presenta nella sua interfaccia pubblica. Ciò a favore del fatto di renderli tutti metodi pubblici statici e di test diretti contro di essi, dimenticando di esporre le funzioni private con le funzioni handle.

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