Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 6 Min.

NUnit, xUnit oder MSTest?

Vergleich der drei Frameworks zum Schreiben von Unit-Tests.
© dotnetpro
Durch den Einsatz von Unit-Tests kann die korrekte Ausführung von Programmcode sichergestellt und seine Verwendung automatisch dokumentiert werden. Schleichen sich Fehler bei nachträglichen Anpassungen des Codes ein, besteht eine gute Chance, diese durch das Netz der Unit-Tests zu entdecken, bevor der Code beim Kunden installiert wird sowie zeitintensive und für das Image des Unternehmens schädliche Support-Sessions erfolgen müssen.Doch welches Framework ist das richtige? Um Sie bei dieser Frage zu unterstützen, werden hier die drei bekannten Frameworks NUnit, xUnit und MSTest miteinander verglichen und gezeigt, wie die einzelnen Frameworks die unterschiedlichen Anforderungen erfüllen.Visual Studio stellt Projektvorlagen für alle drei Testbibliotheken zur Verfügung. Für die Gegenüberstellung wurden Testprojekte mit den Visual-Studio-Vorlagen aus Bild 1 erstellt. Die Entwicklungsumgebung versieht die Testprojekte automatisch mit einer leeren Testklasse, die bereits einen ersten leeren Test anbietet.
Die Visual-Studio-Projektvorlagenfür die drei Unit-Test-Frameworks(Bild 1) © Autor
Bei NUnit wird zusätzlich eine Methode erstellt, die mit dem Attribut Setup dekoriert ist. Diese Methode wird vor jeder Ausführung eines Unit-Tests in der Klasse ausgeführt. Dies ist durchaus hilfreich, wenn es zum Beispiel notwendig ist, die Instanz der zu testenden Klasse – häufig mit SUT abgekürzt, was für System Under Test steht – neu zu erstellen. Welche Möglichkeiten die Frameworks zum Aufräumen nach der Ausführung der Unit-Tests anbieten, erfahren Sie später.Auf den ersten Blick machen die generierten Klassen von NUnit und xUnit einen besonders schlanken Eindruck. Die Methode Setup bei NUnit ist optional und kann deshalb auch gelöscht werden. Aber wie bereits erwähnt, hat eine solche Funktion durchaus ihre Berechtigung. Listing 1 zeigt die drei automatisch erstellten leeren Testklassen.
Listing 1: Automatisch erstellte Testklassen
// NUnit-Testklasse 
public class Tests 
{ 
  [SetUp] 
  public void Setup() { 
  } 

  [Test] 
  public void Test1() { 
    Assert.Pass(); 
  } 
} 


// XUnit-Testklasse 
public class XUnitTest { 
  [Fact] 
  public void Test1()  { 
  } 
} 

// MSTest-Testklasse 
[TestClass] 
public class MsUnitTest { 
  [TestMethod] 
  public void TestMethod1() { 
  } 
}  

Initialisierung vor jedem Test

Wie schon gesagt bietet NUnit mit dem Attribut Setup die Möglichkeit, Initialisierungsroutinen vor jedem Test durchzuführen. Bei xUnit lässt sich der Konstruktor für diese Maßnahmen verwenden, da für jeden in der Klasse definierten Test eine neue Instanz erstellt wird. In Listing 2 sehen Sie die ­xUnit-Implementierung. Sie zeigt zwei Unit-Tests, die eine Klassenvariable inkrementieren, die im Konstruktor mit 0 ini­tialisiert wird. Die Tests sind erfolgreich, wenn die Klassenvariable den Wert 1 hat.
Listing 2: xUnit – Initialisierung
public class XUnitTest 
{ 
  private int _counter; 

  public XUnitTest() { 
    _counter = 0; 
  } 

  [Fact] 
  public void Test1() { 
    _counter++; 
    Assert.True(_counter == 1); 
  } 

  [Fact] 
  public void Test2() { 
    _counter++; 
    Assert.True(_counter == 1); 
  } 
}  
Bei dem Framework aus dem Hause Microsoft wird, wie auch bei NUnit, eine Methode mit einem Attribut markiert. Bei MSTest nennt es sich TestInitializeListing 3 zeigt ein Beispiel dafür.
Listing 3: MSTest – TestInitialize
[TestClass] 
public class MsUnitTest 
{ 
  private int _counter; 

  [TestInitialize] 
  public void Setup() { 
    _counter = 0; 
  } 

  [TestMethod] 
  public void TestMethod1() { 
    _counter++; 
    Assert.IsTrue(_counter == 1); 
  } 

  [TestMethod] 
  public void TestMethod2() { 
    _counter++; 
    Assert.IsTrue(_counter == 1); 
  } 
}  

Bereinigung nach dem Test

Das Bereinigen nach der Ausführung einzelner Tests ist ein weiteres wichtiges Leistungsmerkmal der Frameworks. Zunächst zu NUnit. Im Beispiel in Listing 4 wird ein statischer Zähler mit dem Wert 2 initialisiert und in der Methode Teardown, die mit dem gleichnamigen Attribut markiert ist, wird die Variable dekrementiert und ihr aktueller Wert ausgegeben. Die Ausgaben der beiden Tests im Test Explorer von Visual Studio sehen Sie in Bild 2 und Bild 3.
Listing 4: NUnit – Bereinigung nach dem Test
  private static int _counter = 2; 

  [Test] 
  public void Test1() { 
    Assert.Pass(); 
  } 

  [Test] 
  public void Test2() { 
    Assert.Pass(); 
  } 

  [TearDown] 
  public void Teardown() { 
    --_counter; 
    Console.WriteLine( 
      $"Teardown counter: '{_counter}'"); 
  }  
Ausgabe nachdem zweiten Aufruf bei NUnit(Bild 3) © Autor
NUnit:Ausgabe nach dem ersten Aufruf(Bild 2) © Autor
Um bei xUnit eine Bereinigung nach dem Ausführen der Tests zu ermöglichen, muss IDisposable implementiert werden. Um eine Ausgabe im Ergebnis des Unit-Tests zu erhalten, kommt anstelle von Console.WriteLine(…) eine Instanz von ITestOutputHelper zum Einsatz. Wie in Listing 5 zu sehen ist, wird dazu der Konstruktor um den Parameter ITestOutputHelper ergänzt. Die Ausgaben zeigen Bild 4 und Bild 5.
Listing 5: xUnit – Bereinigung mittels Dispose
public class XUnitTest : IDisposable 
{ 
  private static int _counter = 2; 
  private readonly ITestOutputHelper _outputHelper; 

  public XUnitTest(ITestOutputHelper outputHelper) { 
    _outputHelper = outputHelper; 
  } 

  public void Dispose() { 
    --_counter; 
    _outputHelper.WriteLine( 
      $"Dispose in xUnit counter: '{_counter}'"); 
  } 

  [Fact] 
  public void Test1() { 
    Assert.True(true); 
  } 

  [Fact] 
    public void Test2() { 
      Assert.True(true); 
  } 
}  
Ausgabe nachdem zweiten Aufruf bei xUnit(Bild 5) © Autor
XUnit:Ausgabe nach dem ersten Aufruf(Bild 4) © Autor
MSTest bietet – wie auch NUnit – ein entsprechendes Attribut an. Es heißt bei MSTest TestCleanup. Listing 6 zeigt den zugehörigen Unit-Test und in Bild 6 und Bild 7 finden Sie die zugehörigen Ausgaben.
Listing 6: MSTest – Bereinigung mit CleanUp
  [TestClass] 
  public class MsUnitTest { 
    private static int _counter = 2; 
  [TestCleanup] 
  public void CleanUp() { 
    --_counter; 
    Console.WriteLine( 
      $"CleanUp counter: '{_counter}'"); 
  } 
  [TestMethod] 
  public void TestMethod1() { } 
  [TestMethod] 
  public void TestMethod2() { } 
}  
Ausgabe nachdem zweiten Aufruf bei MSTest(Bild 7) © Autor
MSTest:Ausgabe nach dem ersten Aufruf(Bild 6) © Autor

Mehrfache Ausführung eines Tests

Möchte man eine Funktion auch mit mehreren unterschiedlichen Werten testen, bieten die Frameworks ebenfalls Lösungen an. Listing 7 zeigt für alle drei Testframeworks, wie Sie Tests mehrfach mit unterschiedlichen Parametern ausführen können.
Listing 7: Einen Test mehrfach ausführen
// NUnit 
  [TestCase("Franz", "Huber", 
    ExpectedResult="Franz Huber")] 
  [TestCase("Michi", "Alfons", 
    ExpectedResult ="Michi Alfons")] 

  public string TestWithMultipleValues( 
    string first, string second) { 
    return $"{first} {second}"; 
  } 

// xUnit 
  [Theory] 
  [InlineData("Franz", "Huber", "Franz Huber")] 
  [InlineData("Michi", "Alfons", "Michi Alfons")] 

  public void TestWithMultipleValues(string first, 
    string second, string expected) { 
    Assert.Equal(expected, MethodToTest( 
      first, second)); 
  } 
  private string MethodToTest(string first, 
    string second) { 
    return $"{first} {second}"; 
  } 

// MSTest 
  [DataTestMethod] 
  [DataRow("Franz", "Huber", "Franz Huber")] 
  [DataRow("Michi", "Alfons", "Michi Alfons")] 

  public void TestWithMultipleValues(string first, 
    string second, string expected) { 
    Assert.AreEqual(expected, 
      MethodToTest(first, second)); 
  } 

  private string MethodToTest(string first, 
    string second) { 
    return $"{first} {second}"; 
  }  
Bei NUnit wird dafür die Testmethode mit dem Attribut TestCase dekoriert. Die beiden ersten Werte Franz und Hubert werden bei der Ausführung des Tests mittels der Parameter first und second an den Test übergeben. Der dritte Parameter, der als ExpectedResult gekennzeichnet ist, dient zur Prüfung, ob der Test erfolgreich ausgeführt wurde. Er wird mit dem von der Testmethode gelieferten Rückgabewert verglichen.Bei xUnit verhält es sich ähnlich. An die Stelle des Attributs Fact, welches angibt, dass der Test keine Parameter erwartet und nur einmal ausgeführt wird, wird das Attribut Theory gesetzt. Dieses Attribut gibt an, dass der Test einerseits mehrere Parameter entgegennehmen kann und dass er mehrfach ausgeführt wird. Das erwartete Ergebnis wird hier als Parameter expected an den Test übergeben. Die Auswertung obliegt der Klasse Assert des Unit-Tests.Bei MSTest ist die Vorgehensweise ähnlich wie bei xUnit. Es erfolgt eine Markierung, dass der Test mit Daten ausgeführt wird (Attribut DataTestMethod) und mit dem Attribut DataRow werden, wie auch bei xUnit, sowohl die Eingaben als auch das erwartete Resultat geliefert.

Unit-Tests aus dem CI-Build-Prozess ausnehmen

Mit Unit-Tests können auch Programmteile isoliert entwickelt und geprüft werden, die Abhängigkeiten haben. Sollen beispielsweise Zugriffe auf das Outlook-Objektmodell geprüft werden, etwa um eine neue E-Mail zu öffnen, sollte man die Ausführung des Unit-Tests insofern einschränken, dass dieser nicht beim Daily Build ausgeführt wird. Der Grund: Es könnte vorkommen, dass der Test fehlschlägt, weil Outlook auf dem Build-System nicht installiert ist.Dennoch ist die isolierte Entwicklung mit Unit-Tests möglich. NUnit bietet die Option, einen Test mit dem Attribut Explicit zu kennzeichnen, sodass dieser nur dann ausgeführt wird, wenn sein Start über die Benutzeroberfläche erfolgt.Anders sieht es bei xUnit aus. Hier stellen die Bordmittel keine Lösung bereit. Jimmy Bogard hat unter [1] einen Ansatz veröffentlicht, bei welchem sich durch eine Ableitung der Klasse FactAttribute ein eigenes Attribut definieren lässt, dessen Verwendung dazu führt, dass der Unit-Test nur im Debugger ausgeführt wird (Bild 8).
xUnit-Attribut, um den Test nur im Debugger auszuführen(Bild 8) © Autor
Bei MSTest ist hingegen nichts dergleichen zu finden. Auf StackOverflow wurde der Lösungsansatz vorgeschlagen, siehe [2], in der Testmethode zu prüfen, ob der Debugger angebunden ist:

// Einen MSTest nur im Debugger ausführen 
  [TestMethod] 
  public void TestMethod2() 
  { 
    if (!System.Diagnostics.Debugger.IsAttached) 
      return;

    Assert.IsTrue(true); 
  } 

Test auf erwartete Exceptions

Der letzte Abschnitt befasst sich damit, wie in den drei Frameworks geprüft werden kann, ob eine erwartete Ausnahme ausgelöst wurde und der Test somit als erfolgreich zu werten ist. Die Umsetzung ist in allen drei Frameworks nahezu identisch. Über die Klasse Assert wird die erwartete Ausnahme angegeben und die zu testende Funktion im Delegate angegeben.Um Details der abgefangenen Ausnahme zu prüfen, wird diese als Rückgabewert verfügbar gemacht. Listing 8 zeigt die Varianten der drei Frameworks.
Listing 8: Auf eine Exception prüfen
// NUnit 
  [Test] 
  public void CatchExpectedException() 
  { 
    var ex = Assert.Throws<ArgumentNullException>( 
      () => int.Parse(null)); 
    Assert.That(ex.Message == 
      "Value cannot be null. (Parameter 's')"); 
  } 

// xUnit 
  [Fact] 
  public void CatchExpectedException() 
  { 
    var ex = Assert.Throws<ArgumentNullException>( 
      () => int.Parse(null)); 

    Assert.Equal( 
      "Value cannot be null. (Parameter 's')", 
      ex.Message); 
  } 

// MSTest 
  [TestMethod] 
  public void CatchExpectedException() 
  { 
    var ex = Assert.ThrowsException< 
      ArgumentNullException>( 
      () => int.Parse(null));

    Assert.AreEqual( 
      "Value cannot be null. (Parameter 's')", 
      ex.Message); 
  }  

Fazit

In der Handhabung sind sich die Frameworks sehr ähnlich. Eine umfangreiche Dokumentation, wie sie für NUnit vorhanden ist, sucht man bei xUnit vergeblich. Allerdings sind in Internetforen auch für xUnit zahlreiche Beiträge zu finden. Schlussendlich scheint es eher eine Frage des persönlichen Geschmacks zu sein, welches Framework das geeignete ist. Unter [3] bis [6] finden Sie weitere nützliche Hinweise zu den hier untersuchten Testframeworks.

Fussnoten

  1. Jimmy Bogard, Run tests explicitly in xUnit.net, http://www.dotnetpro.de/SL2105UnitTest1
  2. StackOverflow, MSTest equivalent to NUnit’s Explicit Attribute, http://www.dotnetpro.de/SL2105UnitTest2
  3. Parametrized Tests with MSTest, http://www.dotnetpro.de/SL2105UnitTest3
  4. Roy Osherove, Michael Feathers, Robert C. Martin, The Art of Unit Testing (deutsche Ausgabe), mitp, 2015, ISBN 978-3-82669712-8,
  5. Using XUnit Theory and InlineData to Test C# Extension Methods, http://www.dotnetpro.de/SL2105UnitTest4
  6. MSTest versus NUnit versus XUnit, http://www.dotnetpro.de/SL2105UnitTest5

Neueste Beiträge

DWX Tag 2: Die Devs sind immer schuld - Konferenz
DWX, zweiter Konferenztag: Opening mit der Keynote von Dr. Carola Lilienthal, Interviews und Verlosungen.
3 Minuten
2. Jul 2025
DWX Daily: The Grand Opening - Konferenz
DWX, erster Konferenztag: Zum ersten Mal findet die DWX in Mannheim statt - in neuem Look and Feel. Das wurde gebührend gefeiert.
3 Minuten
1. Jul 2025
00:00
DWX Three takeaways, Ricardo Cachucho: AI is here to stay - Konferenz
Im Kurzinterview nennt Richardo Cachucho die drei wichtigsten Punkte seiner Keynote auf der DWX 25.
2. Jul 2025
Testing & Quality

Das könnte Dich auch interessieren

SOLID versus CUPID – Gegner oder Verbündete? - Softwaredesign
Die SOLID-Prinzipien gelten für Entwicklungsteams als goldene Regeln, um guten Code zu schreiben. Dan North übte 2016 Kritik daran und präsentierte als Gegenentwurf CUPID.
13 Minuten
16. Jun 2025
Mehr Struktur im Begriffschaos - Clean Code und Architektur – Module und Hosts
Clean Code befasst sich, wie der Name verrät, mit Code: Es werden Methoden und Klassen in den Blick genommen. Doch was ist der Fokus von Architektur?
18 Minuten
16. Jun 2025
Die Rückzahlung - Analysieren und zeitnah angehen
Schulden in der Softwareentwicklung können schnell zu großen Bergen anwachsen – die es irgendwann wieder abzubauen gilt. Wie das gehen kann, zeigt dieser Artikel.
7 Minuten
16. Jun 2025
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige