UniTests 4: Dateien schreiben

18. Juni 2012

Nicht alle UnitTests laufen komplett im Code ab. Oft genug hat man Tests die Dateien einlesen, verarbeiten und/oder wieder ausgeben. Klassisches EVA-Prinzip. Aber woher und wohin mit den Dateien in einem UnitTest?

Dieser erste Beitrag zum Thema widmet sich einigen Grundlagen und dem wohin.

 

UnitTests laufen normalerweise in einem Unterverzeichnis unter ./TestResults unter dem Solution-Verzeichnis ab. Dort werden Dateien und ggf. Unterverzeichnisse nach einem gewissen Schema mit Username, Rechnername und Zeitstempel für jeden Testlauf getrennt angelegt. Typisches Beispiel:

image12

Auch wenn viele Entwicklern diese Struktur “schon mal gesehen” haben; das muss nicht heißen, dass ihnen wirklich bekannt ist, was sich dahinter verbirgt…

 

Wo laufen sie denn?

Beginnt man mit einem frischen UnitTest-Projekt, dann hat man zunächst folgende Situation:

Während der Test läuft hat Visual Studio das Testrun-spezifische Unterverzeichnis, samt ./In/{Rechnername} und ./Out angelegt. Das aktuelle Verzeichnis (entsprechend Directory.GetCurrentDirectory()) ist dabei das ./Out Verzeichnis. Und außer einer temporären Datei sind alle Verzeichnisse leer.

Gewöhnungsbedürftig dabei: ./In und ./Out sind offensichtlich aus Sicht von Visual Studio benannt, denn aus Sicht des Tests ist das Verhalten genau umgekehrt: Ausgaben sollten unter ./In landen, Eingangsdaten stehen unter ./Out bereit. Und da ./Out das aktuelle Verzeichnis ist landen Dateien dann oft doch am falschen Ort. (Nachzulesen hier.)

Und noch eine Spezialität: Wenn der Test erfolgreich durchgelaufen ist und niemand Dateien in einem der Unterverzeichnisse angelegt hat, dann wird Visual Studio das ganze Testrun-spezifische Unterverzeichnis wieder löschen. Außer einer analog benannten .trx-Datei (z.B. JungAl_WKJUNGAL09 2012-05-08 20_35_51.trx) mit dem Protokoll der Ergebnisse bliebt nichts übrig.

Im Ausgabe-Verzeichnis sollten nicht nur die vom eigenen UnitTest generierten Dateien landen, auch Visual Studio nutzt dieses Verzeichnis, z.B. für die Ergebnisse der Code Coverage, falls eingeschaltet.

 

Und wo laufen sie jetzt?

Schön zu wissen dass eine solche Struktur existiert, aber wie kann ich in meinem Test darauf zugreifen?

Das UnitTest-Framework verrät einem tatsächlich alles, was man wissen muss – man muss es ihm nur sagen. Alles was dazu benötigt wird ist ein Property vom Typ und Namen TestContext. Dieses Property wird beim Testlauf vor dem Aufrufen der Testmethode selbst gesetzt:

   1: [TestClass]

   2: public class UnitTest

   3: {

   4:     public TestContext TestContext { get; set; }

   5:  

   6:     [TestMethod]

   7:     public void TestMethod1()

   8:     {

   9:         Assert.AreEqual("TestMethod1", TestContext.TestName);

  10:     }

  11:  

  12:     [TestMethod]

  13:     public void TestMethod2()

  14:     {

  15:         Assert.AreEqual("TestMethod2", TestContext.TestName);

  16:     }

  17: }

Natürlich sind die beiden Testmethoden grün Smile.

Dieses Objekt enthält im wesentlichen die Properties, die uns die genannten Fragen beantworten. Insbesondere:

  • Für die Eingangsdaten: _testContext.DeploymentDirectory

    (z.B. .TestResultsJungAl_WKJUNGAL09 2012-05-08 20_35_51Out)

     

  • Für die Ausgabe: _testContext.ResultsDirectory

    (z.B. .TestResultsJungAl_WKJUNGAL09 2012-05-08 20_35_51In)

    Anmerkung: Es gibt darunter das Verzeichnis mit dem Maschinen-Namen, dort legt Visual Studio ggf. die Code Coverage Daten ab.

     

  • Interessant könnten auch FullyQualifiedTestClassName und TestName sein; sie beinhalten den Namen der Testklasse und die Methode. Zwar sind diese im aktuellen Test natürlich bekannt, aber man kann sich damit leichter Hilfsmethoden bauen, die als Eingangsdaten ein TestContext-Objekt übernehmen, mit allen notwendigen Informationen.

 

Schreiben…

Mit diesen Informationen bewaffnet kann man seine Ausgabe entsprechend ablegen:

   1: [TestMethod]

   2: public void TestCreateResultFile()

   3: {

   4:     var data = new DataAccess();

   5:     var result = data.GetBalanceSheets(100);

   6:     string fileName = Path.Combine(this.TestContext.ResultsDirectory, "BalanceSheets.txt");

   7:     var content = result.Select(b => b.ID + " " + b.Rolle + " " + b.Name).ToArray();

   8:     File.WriteAllLines(fileName, content);

   9: }

So einfach Winking smile.

 

Hinweis zu Dateinamen…

Sobald die Zahl der Tests die Ausgaben in Dateien schreiben steigt, kann man leicht den Überblick verlieren, welcher Test nun welche Ausgabe produziert hat. Dann wird es ratsam, Testklasse und Testname in die Dateinamen mit aufzunehmen. Das verhindert zum einen Kollisionen zwischen unterschiedlichen Tests, zum anderen macht es die Zuordnung zum Test deutlich einfacher.

Im Beispiel wäre also etwa TestWithFileAccess.TestCreatResultFile-BalanceSheets.txt die bessere Wahl gewesen.

 

Soweit das Schreiben von Daten. Das Lesen von Eingangsdaten wird im nächsten Beitrag behandelt.