Sonntag, 20. April 2008

Firmenname für neue Projekte in XCode

Wer mit XCode 3.0 das erste Mal ein neues Projekt erstellt, stolpert über kurz oder lang über die Kommentare, die XCode in jede erzeugte Quelldatei einfügt:

//  Created by Thomas Dohmke on 17.04.08.
//  Copyright __MyCompanyName__ 2008. All rights reserved.

Die erste Zeile ist soweit ok (ein vierstelliges Jahr im Datum wäre nett), aber an der zweiten Zeile stört das "__MyCompanyName__", egal ob man nun eine Firma hat oder nicht. Eine ähnliche Ausgabe zeigt auch das Fenster, welches im Menü der Anwendung unter "About NewApplication" erscheint.

Bei einem Blick durch die Projektdateien findet man in der Datei InfoPlist.strings folgenden Eintrag:

NSHumanReadableCopyright = "© __MyCompanyName__, 2008";

Ändert man hier die Zeichenkette "__MyCompanyName__" entsprechend der persönlichen Vorlieben, also z.B.

NSHumanReadableCopyright = "© Thomas Dohmke, 2008";

und kompiliert die Anwendung neu, so zeigt das "About"-Fenster zukünftig den eigenen Namen.

Für die Kommentare in den Quelldateien ist hingegen der folgende Befehl im Terminal einzugeben:

defaults write com.apple.Xcode PBXCustomTemplateMacroDefinitions '{ORGANIZATIONNAME = "Thomas Dohmke";}'

Für bereits existierende Quelldateien hilft das allerdings nicht (hier muss man den Menüpunkt "Edit > Find > Find in Project..." oder Shift+Cmd+F bemühen), aber zumindest alle neuen Quelldateien beinhalten nach einem Neustart von XCode den richtigen Firmennamen.

Samstag, 19. April 2008

Git, iDisk und so

Am 03. Mai 2007 gab Linus Torvalds einen Google Tech Talk Linus Torvalds on git über das ursprünglich von ihm entwickelte Versionskontrollsystem Git. Der Vortrag ist stark geprägt von Linus' persönlichen Ansichten, dennoch war er der Anlass für mich, mal einen Blick auf Git unter Mac OS X zu werfen.

Installation

Für die Installation gibt es gleich mehrere Wege. Man kann den git-osx-installer runterladen, Git selber kompilieren wie im Artikel Installing Git 1.5.2.4 on Mac OS X Leopard beschrieben, oder einfach MacPorts verwenden, das ich eh schon installiert hatte. Mit letzterem genügt es, die Zeile

sudo port install git-core +svn

im Terminal einzugeben und ein paar Minuten zu warten. Nach Abschluss der Installation erzeugt der Befehl

which git

die folgende Ausgabe:

/opt/local/bin/git

Erstellung eines lokalen Repository

Ein lokales Repository lässt sich mit einem einzigen einfachen Befehl erzeugen, sofern man zuvor in das jeweilige Projektverzeichnis gewechselt hat (z.B. ~/Projects/PearApp):

cd ~/Projects/PearApp
git init

Dabei spielt es keine Rolle, ob sich in diesem Verzeichnis schon Dateien befinden. Der Befehl legt lediglich ein neues Verzeichnis .git an, welches sämtliche Dateien des noch leeren Repository enthält.

Hinzufügen von Dateien

Das Hinzufügen einer Datei oder eines Verzeichnisses zum Repository geschieht mit folgenden Befehl:

git add PearApp.xcodeproj

Git durchsucht dabei angegebene Unterverzeichnisse rekursiv und berücksichtigt die darin gefundenen Dateien. Ergo lassen sich alle Dateien eines Projektes mit folgendem Befehl hinzufügen:

git add *

Will man eine ausführlichere Ausgabe erhalten, fügt man vorher den Parameter -v hinzu,

git add -v *

oder lässt sich nachträglich den Status anzeigen:

git status

Schließlich beendet man den Vorgang mittels

git commit -m "Initial revision."

Die Zeichenkette hinter dem Parameter -m beschreibt die Änderungen als so genannte "Commit Message". Lässt man den Parameter weg, so öffnet sich automatisch der bevorzugte Editor, um dort entsprechenden Text einzugeben.

Schieben und Ziehen

Einer der wesentlichen Unterschiede zwischen Git und anderen Versionskontrollsystemen, wie z.B. CVS oder Subversion, ist der dezentrale Ansatz. Es existiert kein zentraler Server, auf dem das einzige Repository gespeichert ist. Stattdessen besitzt jeder Benutzer seine eigene lokale Kopie des Repository. Jede Kopie ist im gewissen Sinne gleichberechtigt, d.h. es existiert per Definition kein Hauptentwicklungszweig des Projektes.

Zurück zum Beispielprojekt. Bisher liegt das erstelle Repository lediglich lokal unterhalb des Projektverzeichnisses. Will man mit mehreren Entwicklern zusammen arbeiten oder einfach seine Daten vor einer kaputten Festplatte, Diebstahl oder dem versehentlichen Löschen des Verzeichnisses bewahren, ist es sinnvoll, eine Kopie auf einem räumlichen getrennten Speicherplatz zu sichern. Ich verwende dafür die iDisk meines .mac-Kontos. Diese lässt sich so einrichten, dass sie auch offline verwendet werden kann und bei einer Verbindung mit dem Internet automatisch synchronisiert wird. Dazu ist in den Einstellungen von .mac lediglich der Button "Start" im Bereich "Synchronisierung der iDisk" zu drücken.

Die iDisk ist fortan im Verzeichnis /Volumes/${USERNAME} ansprechbar, egal ob man online oder offline ist. Das lokale Repository wird über den folgenden Befehl in ein Unterverzeichnis der iDisk geklont,

mkdir /Volumens/${USERNAME}/Repository/
cd ~/Projects/PearApp
git clone --bare . /Volumes/${USERNAME}/Repository/PearApp.git

und anschließend als zusätzliches Repository dem Projekt hinzugefügt:

git remote add idisk /Volumes/${USERNAME}/Repository/PearApp.git

Der Bezeichner nach "add" kann beliebig gewählt werden, das initiale Repository heißt stets "master", für das Repository auf der iDisk fiel meine Wahl auf "idisk".

Zwischen beiden Kopien tauscht man Dateien mittels Ziehen ("pull") und Schieben ("push") aus. Habe ich die Arbeit an einer Datei beendet, übergebe ich die Datei dem lokalen Repository ("commit") und schiebe dieses in das Repository auf der iDisk:

git commit -m "Did some changes."
git push idisk

Umgekehrt ziehe ich Dateien von der iDisk, wenn ich diese beispielsweise im Büro bearbeitet und von dort aus eingecheckt habe:

git pull idisk

Stellt sich noch die Frage, wie bekomme ich überhaupt eine Kopie des Repository im Büro? Nichts einfacher als das:

git clone /Volumes/${USERNAME}/Repository/PearApp.git

Mehr Informationen

Zum Schluss noch ein paar Links zu Git, wovon besonders der letzte für Benutzer von Subversion interessant sein dürfte, beschreibt er doch die Benutzung von Git im Vergleich zu Subversion.

Samstag, 12. April 2008

Entwicklermenü in Safari

Gibt man folgende Befehle im Terminal seines Mac ein,

defaults write com.apple.Safari WebKitDeveloperExtras -bool 
defaults write com.apple.Safari IncludeDebugMenu 1

und startet anschließend Safari neu, so erscheint ein neues Menü, welches mit "Develop" betitelt ist.

Neben Menüpunkten zum Manipulieren der Browser-Kennung sowie zum Deaktivieren von Skripten, Bildern und Caches usw. wird auch die Möglichkeit angeboten, die so genannte Netzwerk-Timeline einzublenden. Damit öffnet sich ein zusätzliches Fenster, in dem die Ladezeit der jeweiligen Webseite mit alle zugehörigen Dateien visualisiert wird. Nachfolgend die Ladezeit dieses Blogs als Beispiel, neben dem eigentlichen Dokument wird die meiste Zeit offenbar beim Laden meiner del.icio.us-Links verbraten.

Freitag, 11. April 2008

Veränderungen an diesem Blog

Noch zum Jahresende war ich zuversichtlich, mich wieder mehr diesem Blog widmen zu können. Was ich nicht berücksichtigt hatte: Mit dem Ende meiner Dissertation ist auch das Interesse an MATLAB etwas geschwunden. Nach 5 Jahren mit Projekten im modellbasierten Bereich war es an der Zeit für etwas Neues, und so driftete ich zuletzt durch diverse Programmierthemen wie Objective-C, Cocoa, Ruby, Python und Git, um nur einige zu nennen. Als Konsequenz habe ich beschlossen, das Themenfeld dieses Blogs zu erweitern. Statt nur über MATLAB zu schreiben, werde ich zukünftig über alles das berichten, was mich im Hinblick auf Softwareentwicklung so interessiert.

Wer dennoch zukünftig nur an Artikeln zu MATLAB interessiert ist, der möge bitte über das Schlüsselwort matlab filtern oder seinen Feed wie folgt ändern: MATLAB-Feed (RSS).

mlUnit 2.0 Beta

Vor ein paar Tagen habe ich die neueste Version von mlUnit (genannt "2.0-beta1") bei SourceForge veröffentlicht. mlUnit 2 ist die Re-Implementierung von mlUnit mit der neuen objektorientierter Syntax von MATLAB R2008a. Daraus ergeben sich drei wesentliche Unterschiede zur alten Version: (1) man kann mehrere Testmethoden einer Testfallklasse nun in einer einzelnen Datei definieren; (2) man kann mehrere solcher Testfallklassen in einem Paket mit einem eigenen Namensraum zusammenfassen; (3) man benötigt zwingend MATLAB R2008a oder neuer.

Die Installation erfolgt durch den Download des bereitgestellten Archivs, das Entpacken in einen beliebigen Ordner und dem Hinzufügen des Quellverzeichnisses zum MATLAB-Pfad:

>> addpath('$MLUNIT\mlunit2\src');

Mit $MLUNIT wird hierbei das Rootverzeichnis von mlUnit 2 bezeichnet.

Ein Beispieltestfall mit mlUnit 2 sieht wie folgt aus:

>> edit test.sin.m 
    classdef test_sin < mlunit.test_case 
        methods 
            function self = test_sin(varargin) 
                self = self@mlunit.test_case(varargin{:}); 
            end 
 
            function self = test_null(self) 
                mlunit.assert_equals(0, sin(0)); 
            end 
 
            function self = test_sin_cos(self) 
                mlunit.assert_equals(cos(0), sin(pi/2)); 
            end 
        end 
    end

Es wird die Testfallklasse test_sin erstellt, die von der Basisklasse test_case abgeleitet ist, und über zwei Testmethoden verfügt: test_null und test_sin_cos. Wie man sieht, sind die Klassen und Methoden von mlUnit selbst nun in dem gleichnamigen Paket gekapselt. Dadurch werden Konflikte mit MATLAB-eigenen Methoden verhindert.

Der Testfall wird wie folgt ausgeführt:

>> runner = mlunit.text_test_runner(1, 1); 
>> loader = mlunit.test_loader; 
>> run(runner, load_tests_from_test_case(loader, 'test_sin')); 

Wie auch schon bei mlUnit 1.5 werden alle Testfälle, die während der Entwicklung von mlUnit entstanden sind, mitgeliefert. In der neuen Version sind diese im Paket mlunit_test enthalten:

>> addpath('$MLUNIT\mlunit2\test');
>> mlunit_test.run;

Zum Schluss noch ein Hinweis in eigener Sache: mlUnit 2.0-beta1 ist ein Beta-Release. Bitte melden Sie Fehler per E-Mail an thomas@dohmke.de (am besten "mlunit" im Betreff verwenden), hinterlassen Sie einen Kommentar zu diesem Artikel oder verwenden Sie den Bugtracker des Projekts. Danke.

Mittwoch, 5. März 2008

Objektorientierte Programmierung mit MATLAB R2008a

Wie ich schon in meinem letzten Beitrag schrieb, wurde in der neuen MATLAB-Version R2008a die Art und Weise der objektorientierten Programmierung mit der Programmiersprache m verändert. Dazu wurde eine völlig neue Syntax definiert, die im folgenden vorgestellt werden soll.

Bis Version R2007b war es notwendig, für eine Klasse ein neues Verzeichnis zu erzeugen, dessen Name mit einem @ beginnt. Diese Vorgehensweise kann auch weiterhin verwendet werden, und zwar immer dann, wenn die Methoden der Klasse auf mehrere Dateien verteilt werden soll. Ansonsten kann eine Klasse nun auch in einer einzelnen Datei untergebracht haben, die nach dem Bezeichner der Klasse benannt wird. Als Beispiel soll eine Klasse für ein Testergebnis dienen. Der Name der Klasse ist "test_result", dementsprechend wird eine Datei mit dem Namen "test_result.m" erstellt:

classdef test_result
end

Die Anweisung "classdef" leitet die neue Klasse ein, es folgt der Name des Klasse und in üblicher MATLAB-Manier wird der Block mit einem "end" beendet. Mit dieser simplen Konstruktion ist die Klasse bereits funktionsfähig und kann auf der Kommandozeile instanziiert werden (vorausgesetzt, die Datei "test_result.m" ist entweder im aktuellen Verzeichnis oder im MATLAB-Pfad auffindbar):

>> result = test_result	

result = 

test_result with no properties.
list of methods

Wie MATLAB richtig bemerkt, hat die Klasse noch keine Eigenschaften. Das Wort "methods" wird hingegen als Link dargestellt und klickt man darauf, erscheint folgende Ausgabe mit dem Standardkonstruktor der Klasse:

Methods for class test_result:

test_result  

Zur Definition einer Eigenschaft wurde der Block "properties" eingeführt, für Methoden der Block "methods":

classdef test_result
    properties
        should_stop = 0;
    end
    
    methods
        function should_stop = get_should_stop(self)
            should_stop = self.should_stop;
        end
    end
end

Im Beispiel wird die Eigenschaft "should_stop" definiert, die innerhalb des Testergebnisses anzeigt, dass die Testausführung beendet werden soll. Auf die Eigenschaft kann mittels der Methode "get_should_stop" zugegriffen werden.

Wie schon in R2007b und allen vorherigen Versionen, müssen bei der Veränderung einer Klasse alle Objekte dieser Klasse aus dem Workspace entfernt werden. Immerhin reicht aber jetzt ein einfaches "clear" statt dem bisherigen "clear all":

>> clear
>> result = test_result

result = 

test_result

properties:
    should_stop: 0

list of methods

Im Gegensatz zur alten Programmierweise ist es nun auch erlaubt, direkt auf die Eigenschaften zuzugreifen:

>> result.should_stop

ans =

     0

Soll ein solcher Zugriff unterbunden werden, kann der "properties"-Block mit einem zusätzlichen Attribut versehen werden:

classdef test_result
    properties (GetAccess = private)
        should_stop = 0;
    end
    
    methods
        function should_stop = get_should_stop(self)
            should_stop = self.should_stop;
        end
    end
end

Damit wird der Zugriff auf "should_stop" als privat deklariert, alternativ ist auch das Schlüsselwort "protected" erlaubt, welches den Zugriff auf ein Paket beschränkt (siehe unten). Das ganze führt zu einem interessanten Effekt:

>> clear
>> result = test_result

result = 

test_result with no properties.
list of methods

Laut dieser Aussage hat test_result nun keine Eigenschaften mehr. Eine Lesezugriff auf "should_stop" ist nur noch über die definierte Methode möglich:

>> result.should_stop
??? Getting the 'should_stop' property of the 'test_result' class is not allowed.

>> result.get_should_stop

ans =

     0

Ein Schreibzugriff ist aber weiterhin möglich:

>> result.should_stop = 1;
>> result.get_should_stop

ans =

     1

Soll auch dieser verhindert werden, muss auch das Attribut "SetAccess" entsprechend auf "private" gesetzt werden.

classdef test_result
    properties (GetAccess = private, SetAccess = private)
        should_stop = 0;
    end
    
    methods
        function should_stop = get_should_stop(self)
            should_stop = self.should_stop;
        end
    end
end

Neben "GetAccess" und "SetAccess" existieren diverse weitere Attribute, u.a. um Eigenschaften als Konstanten zu definieren (sprich als statische Eigenschaften) oder um diese von der Serialisierung auszuschließen. Will man Eigenschaften mit verschiedenen Attributen auszeichnen, fügt man einfach mehrere "properties"-Blöcke ein:

classdef test_result
    properties
      errors = {};
    end
    properties (GetAccess = private, SetAccess = private)
        should_stop = 0;
    end
    
    methods
        function should_stop = get_should_stop(self)
            should_stop = self.should_stop;
        end
    end
end

Ferner ist es möglich, spezielle Methoden für den Zugriff auf eine Eigenschaft zu erstellen. Dies ist insbesondere dann sinnvoll, wenn vor dem Zuweisen oder Lesen weitere Schritte ausgeführt werden sollen, beispielsweise eine Prüfung des Wertebereichs.

Auch bei Methoden hat sich einiges getan. Oben wurde bereits auf die Methode "get_should_stop" mit der neuen Syntax verwendet, es ist aber auch weiterhin die alte Variante möglich:

>> get_should_stop(result); % alt
>> result.get_should_stop;  % neu

Ebenfalls im "method"-Block kann der Konstruktor der Klasse definiert werden, wobei die gleiche Syntax wie bei einer normalen Funktion verwendet wird. Der Name der Funktion muss dem Namen der Klasse entsprechen, der Rückgabewert muss stets eine Instanz der Klasse sein.

Will man explizit eine Methode der jeweiligen Superklasse aufrufen, d.h. einer Klasse, von der die verwendete Klasse per Vererbung abgeleitet ist, muss dem Namen der Methode ein @ und dann der Name der Superklasse folgenden. Beispiel:

classdef text_test_result < test_result
    methods
        function should_stop = get_should_stop(self)
	    fprintf(1, 'Should stop.\n');
            should_stop = get_should_stop@test_result(self);
        end
    end
end

Die Klasse "text_test_result" wird hier von der Klasse "test_result" abgeleitet. Die Methode "get_should_stop" gibt zunächst eine Meldung auf der Standardausgabe aus, und ruft dann selbige Methode der Superklasse auf.

Wie auch bei den Eigenschaften lassen sich neuerdings zusätzliche Attribute zu jedem "method"-Block hinzufügen, die den Zugriff auf die enthaltenen Methoden limitieren können sowie sie als statisch, abstrakt oder endgültig (MathWorks nennt das "sealed", bei Java würde man "final" schreiben) markieren.

Schließlich können Klassen in Paketen organisiert werden. Dazu muss die Klassendatei in ein Verzeichnis verschoben werden, dessen Name mit einem + beginnt und nachfolgend dem Namen des Paketes entspricht. Verschiebt man die Beispieldatei "test_result.m" in das Verzeichnis "+mlunit", welches außerdem dem MATLAB-Pfad hinzugefügt wird, so gehört die Klasse "test_result" nun dem Paket "mlunit" an. Entsprechend wird sie über folgenden Aufruf verwendet:

>> result = mlunit.test_result;

Ferner ist es möglich, ein oder mehrere Pakete innerhalb einer Methode (und nur dort) zu importieren:

function test_foobar()
    import mlunit.*;
    result = test_result();
end

Etwas merkwürdig ist jedoch, dass auch innerhalb eines Paketes dessen Name referenziert werden muss. Obiges Beispiel für die Klasse "text_test_result" wird demnach zu:

classdef text_test_result < mlunit.test_result
    methods
        function should_stop = get_should_stop(self)
	    fprintf(1, 'Should stop.\n');
            should_stop = get_should_stop@mlunit.test_result(self);
        end
    end
end

Die Referenz auf den Paketnamen innerhalb von "get_should_stop" lässt sich wiederum durch die "import"-Anweisung verhindern, während die erbende Klasse in der ersten Zeile stets den Paketnamen tragen muss (jedenfalls habe ich keine Möglichkeit gefunden, dies zu umgehen).

Damit endet dieser erste Einblick in die neue objektorientierten Programmierung mit MATLAB R2008a, der aber keinesfalls umfassend ist. Insbesondere fehlen noch die neuen "handle"-Klassen sowie die Möglichkeit, neben Eigenschaften und Methoden auch Ereignisse ("events") zu definieren. Sobald ich mir diese genauer angeschaut haben, wird es deshalb einen weiteren Artikel geben.

Auch kann man die Beispiele in diesem Artikel durchaus als Hinweis deuten, das mlUnit 2.0 in Arbeit ist... :)

Samstag, 1. März 2008

R2008a

Da habe ich wohl falsch getippt, als ich vor ein paar Tage mutmaßte, dass das neue MATLAB-Release am Montag erscheint. Zwar funktioniert der Download heute erst ab 18:00 Uhr (EST), sprich 00:00 Uhr (MEZ), aber die neuen Features werden schon auf der Seite What's New In Release 2008a vorgestellt. Highlight aus meiner Sicht sind die neuen Fähigkeiten für "state-of-the art object-oriented programming". Endlich entfällt das umständliche Definieren von Klassen in separaten Verzeichnissen und das Verteilen der Methoden auf einzelne Dateien, mit "handle classes" können Parameter als Referenz übergeben werden und der MATLAB Desktop unterstützt das Erzeugen von Klassen durch ein Template. Sobald ich die neue Version ein wenig ausprobieren konnte, werde ich einen ausführlichen Artikel darüber schreiben, und mich dann an die Arbeit für Version 2.0 von mlUnit machen. :)

Mittwoch, 27. Februar 2008

Neues Simulink-Blog bei MathWorks

Nicht mehr lange, dann müsste, wenn es nach dem Release Scedule von MathWorks geht, das nächste MATLAB-Release R2008a erscheinen (ich tippe mal auf den 03.03.2008, sofern sie in Massachusetts nicht auch samstags arbeiten). Jedenfalls ist gestern erstmal ein neues Blog veröffentlicht worden, Seth on Simulink, das sich offenbar mit Simulink beschäftigen wird. In seinem ersten Post Welcome gibt er unter anderem einen Überblick über die kommenden Themen:

solvers, sample times, modeling, hacks, blocks, model reference, libraries, masking, custom code

"hacks" könnte ganz spannend werden, ansonsten fehlen mir ein wenig die Themen für Fortgeschrittene wie z.B. Codegenerierung. Und von Testen ist auch keine Rede...

Freitag, 15. Februar 2008

slUnit: Der Testkontext

Textkontexte ermöglichen die Wiederverwendung von gemeinsamen Code mehrerer Testmethoden. Gewöhnlich werden sie im Zusammenhang mit der Klasse TestCase verwendet, die jeweils einen Textkontext mit den zugehörigen Testmethoden kapselt. Das Ziel eines Textkontextes ist die Vermeidung von doppeltem Code. Zudem stellt er sicher, dass die Ausführung einer Testmethode unabhängig von der Ausführung anderer Testmethoden ist, speziell auch dann, wenn die Tests auf externe Ressourcen zugreifen. Der wichtigste Teil des Testkontexts sind daher Methoden zur Vor- und Nachbereitung (zum "Aufräumen") eines Tests.

Konzept (xUnit)

Die xUnit-Familie sieht zwei Methoden für den Testkontext vor: set_up und tear_down. Der folgende Pseudocode zeigt zwei Beispieltestmethoden, die einen einfachen Schalter mit zwei Zuständen testen: Die Testmethode test_off prüft den ausgeschalteten Zustand (der Default-Zustand), die Testmethode test_on das Einschalten.

external ignition; 
var switch;
 
set_up() { 
  switch = new generic_switch(ignition); 
} 

test_off() { 
  assert_equals(false, switch.is_on()); 
} 

test_on() { 
  switch.on(); 
  assert_equals(true, switch.is_on()); 
} 

tear_down() { 
  switch.off(); 
}

Eine Dopplung der Variable switch wird hier verhindert, indem diese bereits in set_up angelegt wird. Als Beispiel einer externen Ressource wird diese Variable mit der Zündung eines Autos verbunden, die im Beispiel durch den Bezeichner ignition angedeutet wird. Da test_on den Schalter und damit die Zündung einschaltet, muss nach der Ausführung dieser Testmethode der Schalter wieder ausgeschaltet werden. Ansonsten schlägt die Ausführung von test_off fehl, wenn diese nach test_on ausgeführt wird. Das Ergebnis von test_off wäre also nicht mehr unabhängig von test_on. Folglich wird der Schalter in tear_down ausgeschaltet, wobei xUnit sicherstellt, dass diese Methode stets nach einer Testmethode und unabhängig von deren Ergebnis ausgeführt wird (also auch, wenn die Testmethode selbst mit einem Fehler beendet wird).

Realisierung (slUnit)

In slUnit werden Textkontexte durch die Aggregation von Testfällen in einen Testverbund realisiert. Damit ist es möglich, gemeinsamen Code der Testfälle innerhalb des Testverbundes zu kapseln. Die Ausgänge eines Testfalls sind dann nicht länger nur die Eingänge des Testobjekts, sondern können auch mit dem gemeinsam genutzten Code verbunden werden, dessen Ausgänge wiederum die Eingänge des Testobjekts und/oder der Testfälle sein können. Der Testverbund in der Abbildung im Artikel slUnit: Tests definieren und ausführen verwendet dieses Konzept.

Das Herstellen und Auflösen eines Textkontextes, wie er durch die Methoden set_up und tear_down erreicht wird, ist für modellbasierten Code, d.h. Simulink-Blöcke, nicht notwendig. Die einzige Ausnahme stellt die Interaktion mit der MATLAB-Umgebung dar, beispielsweise durch das Verwenden von MATLAB-Variablen in Konstanten. Die Integrität des Textkontextes kann dafür durch die von Simulink bereitgestellten Callback-Funktionen StartFcn und StopFcn sichergestellt werden, typischerweise indem in StartFcn die Parameter aus einer Datei geladen und in StopFcn die geladenen Werte zurückgesetzt werden.

Weitere Artikel in der Reihe

Fragen, Kommentare, Fehler

Bei Fragen, Kommentaren oder Fehlern bitte einfach eine E-Mail an thomas@dohmke.de senden oder einen Kommentar hier im Blog hinterlassen. Gewöhnlich antworte ich innerhalb von 24 Stunden. Spam und vergleichbarer Müll wird automatisch aussortiert, daher bitte einen aussagekräftigen Betreff, z.B. "Frage zu slUnit", verwenden.

Sonntag, 10. Februar 2008

slUnit: Tests definieren und ausführen

Die Art und Weise der Erstellung und Ausführung von Testfällen ist eine der wesentlichen Eigenschaften eines Testing Frameworks, da sich aus ihr die grundlegende Architektur ergibt. Dieser Artikel beschreibt die Darstellung eines Testfalls in xUnit als generisches Konzept und anschließend die Realisierung dieses Konzepts in slUnit.

Konzept (xUnit)

Ein einzelner Testfall wird in xUnit durch eine Methode (Test Method) repräsentiert, deren Name mit "test" beginnt. Mittels eines solchem designierten Namens ist es einer Instanz der Klasse Test Runner möglich, alle auszuführenden Testmethoden zu lokalisieren. Alternativ können in modernen Programmiersprachen auch sogenannte Annotations für diese Kennzeichnung verwendet werden, beispielsweise wird mit Java 5 und JUnit 4 eine Testmethode wie folgt definiert:

@Test public void emptyStack() {
    stack = new Stack();
    assertTrue(stack.isEmpty());
}

Die Annotation @Test markiert hier die Methode emptyStack als eine Testmethode.

Im Allgemeinen kann eine Testmethode eine beliebige Anzahl von Zusicherungen enthalten, die nacheinander geprüft werden. Die Testmethode schlägt fehl, sobald eine der Zusicherungen falsch ist. Alle nachfolgenden Zusicherungen werden dann nicht mehr ausgeführt, da diese üblicherweise voneinander abhängig sind, z.B.:

assert_not_equals(0, foo(x));
assert(0 < (1 / foo(x)));

Zudem kann eine Testmethode auch die strukturellen Fähigkeiten der jeweiligen Programmiersprache verwenden, beispielsweise if-Verzweigungen und while-Schleifen, und als Teil dieser Zusicherungen bedingt oder zyklisch ausführen.

Typischerweise werden Testmethoden innerhalb von Klassen organisiert, da die meisten Testing Frameworks, die auf xUnit basieren, in einer objektorientieren Programmiersprache realisiert sind. Eine Klasse kapselt dabei solche Testmethoden, die sich denselben Testkontext teilen. Der Begriff Testkontext dient hier als Übersetzung des englischen Begriffes Test Fixture; er beschreibt den Kontext, den die Testfälle zur Ausführung und zur Überprüfung der Zusicherungen voraussetzen. Die Basisklasse für Testmethoden wird Test Case genannt. Instanzen dieser Klasse werden in Objekten der Klasse Test Suite zusammengefasst.

Für die Ausführung der Testfälle stellen die Mitglieder der xUnit-Familie verschiedene kommandozeilenbasierte oder grafische Anwendungen zur Verfügung, die alle von der Basisklasse Test Runner abgeleitet sind und dadurch eine einheitliche Schnittstelle realisieren. Die auszuführenden Tests können durch verschiedene Techniken ausgewählt werden:

Aufzählung (Enumeration)
Jede Testmethode wird manuell hinzugefügt, in dem der Konstruktor der Testklasse mit dem Namen der Testmethode aufgerufen und so die Klasse instanziiert wird. Damit ist es außerdem möglich, nur eine einzige Testmethode als Test auszuführen.
Auffindung (Discovery)
Die Testmethoden werden automatisch durch den Test Runner ermittelt, in dem die obige Namenskonvention oder Annotations verwendet werden.
Auswahl (Selection)
Als Ergänzung zur letzten Technik werden dem Test Runner zusätzliche Kriterien zur Auswahl der Testmethoden übergeben, so dass eine Untermenge der Tests ausgeführt wird. Solche Kriterien basieren üblicherweise auf spezifischen Annotations für die Testklassen oder Testmethoden.

Realisierung (slUnit)

Das Design von slUnit setzt diese klassenbasierte Architektur von xUnit um, in dem sowohl das Testobjekt als auch die Testfälle als Simulink-Subsysteme repräsentiert werden. Ein Subsystem für eine Testmethode wird mit einem spezifischen Mask-Type markiert, um es von anderen Subsystemen zu unterscheiden. Der Mask-Type ist ein Attribut, das Subsysteme in Simulink markiert. Man kann es mit den Annotations vergleichen, die in anderen xUnit-Frameworks verwendet werden. Neben Testmethoden werden auch die Klassen Test Case und Test Suite durch Subsysteme dargestellt. Tatsächlich gibt es zwischen diesen beiden Typen keine Unterschiede, so dass das zugehörige Subsystem nachfolgend als Testverbund (Test Composite) bezeichnet wird. Ein Testverbund beinhaltet mindestens ein weiteres Subsystem, das entweder eine Testmethode oder wiederrum ein Testverbund ist. Auch das Simulink-Modell selbst wird als Testverbund betrachtet, allerdings mit der Besonderheit, dass dieses auch das Testobjekt enthält. Die nachfolgende Abbildung zeigt die entsprechende Architektur eines slUnit-Modells.

Die Tests werden nun ausgeführt, indem das Simulink-Modell simuliert wird. Während der Simulation werden die Signale und Zustände des Modells für eine Anzahl von Zeitschritten berechnet werden. Eine einzelne Simulation entspricht der Ausführung eines einzelnen Testfalls. Da das Modell mehrere Testfälle enthält, muss sichergestellt werden, dass nur die Assert-Blöcke der jeweils aktiven Testmethode ihre Zusicherungen überprüfen. Alle anderen Assert-Blöcke sollen automatisch deaktiviert werden. Diese Funktionalität wird durch den Multiplexer-Block realisiert, der daher als das wichtigste Element des slUnit-Frameworks angesehen wird. Er aktiviert die Assert-Blöcke, die sich im Subsystem der aktiven Testmethode befinden, und deaktiviert alle anderen Assert-Blöcke. Zudem verbindet er den Ausgang der Testmethode mit dem Eingang des Testobjekts. Wie in der obigen Abbildung dargestellt, beinhaltet jeder Testverbund besitzt genau einen Multiplexer-Block. Dieser muss nicht zwangsläufig den Ausgang des Subsystems darstellen, sondern kann auch mit weiteren Subsystemen verbunden sein, die gemeinsame Funktionalitäten ("Common Code") aller vom Multiplexer berücksichtigen Testfälle beinhaltet.

Wie bereits erwähnt, wird ein einzelner Testfall durch Starten der Simulation ausgeführt. Sollen indes mehrere Testfälle ausgeführt werden, kann dies ähnlichen wie bei xUnit realisiert werden. Einerseits beinhaltet jeder Testverbund einen Button "Run All", der alle zugehörigen Testfälle ausführt. Andererseits können die Testfälle mit zusätzlichen Blöcken versehen werden, die über keine eigentliche Funktion verfügen, sondern den Testfall lediglich über den Namen des Blocks und/oder dessen Farbe markieren. Ein solcher Block stellt eine Art Annotation. Durch Doppelklick des Blocks können dann alle Testfälle ausgeführt werden, die einen gleichen Block enthalten. Damit ist eine Bildung von Gruppen von Testfällen möglich, die orthogonal zur Hierachie aus Testfällen und Testverbünden steht.

Das Testergebnis wird als Hintergrundfarbe des Subystems einer Testmethode dargestellt. Dieser Hintergrundfarbe wird von der Hintergrundfarbe und damit den Ergebnissen der Assert-Blöcke geerbt. D.h. die Hintergrundfarbe wird auf rot gesetzt, wenn mindestens eine Zusicherung fehlgeschlagen ist. In gleicher Art und Weise wird die Hintergrundfarbe des Subsystems eines Testverbunds aus den Ergebnissen aller enthaltenen Testfälle gebildet.

Weitere Artikel in der Reihe

Fragen, Kommentare, Fehler

Bei Fragen, Kommentaren oder Fehlern bitte einfach eine E-Mail an thomas@dohmke.de senden oder einen Kommentar hier im Blog hinterlassen. Gewöhnlich antworte ich innerhalb von 24 Stunden. Spam und vergleichbarer Müll wird automatisch aussortiert, daher bitte einen aussagekräftigen Betreff, z.B. "Frage zu slUnit", verwenden.