2011-01-11 5 views
12

Ich habe in letzter Zeit mit der vollständigen Integration von kontinuierlichen Tests in meinen Matlab-Entwicklungszyklus gebastelt und bin dabei auf ein Problem gestoßen, von dem ich nicht weiß, wie ich es schaffen soll. Wie fast alle Benutzer wissen, versteckt Matlab die Unterfunktionen in einer M-Datei aus der Sicht von Funktionen außerhalb dieser M-Datei. Ein Spielzeugbeispiel kann unten gesehen werden:Was ist der einfachste Weg, M-Datei-Unterfunktionen für Komponententests verfügbar zu machen?

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

Ich möchte Unit Tests auf Subfunc selbst durchführen. Dies ist AFAIK unmöglich, weil ich es von keiner externen Funktion aus aufrufen kann.

Ich benutze derzeit Matlab xUnit von Steve Eddins und kann dieses Problem nicht umgehen. Die einfache Lösung - Subfunktion in eine eigene M-Datei aufteilen - ist in der Praxis nicht akzeptabel, da ich zahlreiche kleine Funktionen habe, die ich testen möchte und nicht mein Dateisystem mit jeweils einer separaten M-Datei verschmutzen möchte . Was kann ich tun, um einfache Komponententests zu schreiben und durchzuführen, ohne neue Dateien für jede Funktion zu erstellen, die ich testen möchte?

Antwort

1

Ich habe einen ziemlich hacky Weg, dies zu tun. Nicht perfekt, aber zumindest ist es möglich.

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 

dann an der Eingabeaufforderung dies den Trick tun wird:

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

Das heißt, ich bin die subfuncs an :) all – William

12

Was müssen Sie im Allgemeinen zu tun ist function handles innerhalb der primären Funktion, um Ihre Unterfunktionen bekommen und sich außerhalb der Funktion übergeben, wo Sie Kann Einheit sie testen. Eine Möglichkeit, dies zu tun, besteht darin, Ihre primäre Funktion so zu modifizieren, dass bei einer bestimmten Menge von Eingabeargumenten (d. H. Keine Eingaben, irgendein Flag-Wert für ein Argument usw.) die von Ihnen benötigten Funktionen zurückgegeben werden.

Zum Beispiel können Sie ein paar Zeilen Code zu Beginn Ihrer Funktion hinzuzufügen, so dass sie alle von der Unterfunktion gibt verarbeitet, wenn keine Eingabe angegeben wird:

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 

Oder, wenn Sie die Angabe lieber ein Eingabeflag (um Verwechslungen zu vermeiden, die mit versehentlich beim Aufrufen der Funktion ohne Eingaben verbunden sind, wie Jonas in seinem Kommentar erwähnt), könnten Sie die Subfunktionshandles zurückgeben, wenn das Eingabeargument data eine bestimmte Zeichenkette ist. Beispielsweise könnten Sie die Eingabeüberprüfungslogik im obigen Code wie folgt ändern:

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

+1 nicht aussetzt Ich würde sagen, dass dies der beste Weg, um darüber zu gehen ist (einen Eingang Flag, das Prüfmodus [wie ein versteckten auslösen/undokumentierte Funktion]) ... – Amro

+3

+1 - obwohl ich die Funktions-Handles mit einem 'returnSubfunctionHandles' Eingabeargument erstellen würde. Das Aufrufen einer Funktion, die andernfalls eine Eingabe mit null Eingabeargumenten erfordern würde, ist ein Fehler, der gelegentlich auftreten kann, und es kann eine Weile dauern, herauszufinden, woher diese komischen Griffe kommen. – Jonas

+0

Ich fand diese Antwort sehr hilfreich und schrieb eine verwandte Frage darüber, wie die Liste der Unterfunktionen automatisch generiert werden kann. Ab 2013b gibt es eine einfache Möglichkeit, dies über 'localfunctions' zu tun: http://stackoverflow.com/questions/37508773/list-subfunctions-defined-in-function-file-within-calling-environment – Alex

1

Ich verwende eine Methode, die die Art und Weise spiegelt, wie GUIDE seine Eingabemethoden generiert. Zugegeben ist es in Richtung GUIs voreingenommen ...

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 

Auf diese Weise können Sie die folgende

% Calls bar in foo tun vorbei 10 und 1
foo('bar', 10, 1)

1

Haben Hast du die neuen Klassen benutzt? Sie können diese Funktion in eine statische Methode für eine Dienstprogrammklasse umwandeln. Dann könnten Sie entweder die Unterfunktionen in andere statische Methoden umwandeln oder die Unterfunktionen in lokale Funktionen für die Klasse umwandeln und der Klasse eine statische Methode übergeben, die ihnen die Handles zurückgibt.

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 

Wenn Sie die classdef Stil Syntax verwenden, all diese Funktionen und alle anderen Methoden, die alle in einer einzigen Datei fooUtil.m gehen; kein Dateisystem-Durcheinander. Oder, anstatt die privaten Daten zu veröffentlichen, könnten Sie den Testcode in der Klasse schreiben.

Ich denke, die Unit Tests Puristen werden sagen, dass Sie dies überhaupt nicht tun sollten, weil Sie gegen die öffentliche Schnittstelle eines Objekts testen sollten, und wenn Sie die Subparts testen müssen, sollten sie zu etwas ausgeklammert werden sonst präsentiert sie sie in ihrer öffentlichen Schnittstelle. Dies spricht dafür, alle öffentlichen statischen Methoden zu machen und direkt gegen sie zu testen, wobei vergessen wird, private Funktionen mit Funktionsgriffen auszusetzen.

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