Laden...

UnitTests: Strukturierung und Bennenung der Tests/Testprojekte/Testmethoden

Letzter Beitrag vor 13 Jahren 30 Posts 12.190 Views
UnitTests: Strukturierung und Bennenung der Tests/Testprojekte/Testmethoden

Ich arbeite mich derzeit endlich in UnitTests ein und hätte direkt ein paar kleine Fragen.

  1. Ich habe eine Solution mit 4 Projekten, sollte ich auch 4 test Projekte machen?
  2. Die Projekt Namensgebung mach ich immer "<2Test>.Test" - angemessen?
  3. Klassen benenne ich immer <Name>Test - ok?
  4. Wie benennt man die Methoden am besten? Ich schreibe bisher immer den namen der zu testende Methode und was erwartet wird in den titel, z.B.:
private class NumberGeneratorTest
{
    [TestMethod]
    private void Generate_CreatesANumberGenerator()
    {
        NumberGenerator generator = NumberGenerator.Generate();
        Assert.IsNotNull(generator, "The NumberGenerator wasn't generated properly");
    }

    [TestMethod]
    private void Next_ReturnsNumberBelow5()
    {
        NumberGenerator generator = NumberGenerator.Generate();
        int generatedNumber = generator.Next();
        Assert.IsTrue(generatedNumber < 5, "The generated number is bigger then 5");
    }
}

Oft sehe ich auch einfach ein NextTest(); also die selbe Namensgebung wie bei der Klasse. Wenn man das tut, wie benennt man unterschiedliche cases der selben Methode?

  1. Wie verträgt es sich mit DRY? z.B. wenn ich Next_ReturnsNumberBelow5 mit einer anderen Zahl aufrufen will.
  2. Wie findet ihr die Syntax des Codes in Punkt 4?

LG
CSL

hallo,

ohne da jetzt die "ultimative wahrheit" sagen zu können 😉, schreibe ich halt mal, was ich dazu denke:

ad 1) Würde ich machen

ad 2) Die Testprojekte heißen bei uns wie das zu testende Assembly + "Tests"

ad 3) Mach ich auch so, jedoch <ClassName> + "Tests".

ad 4) <FunktionsName><Bedingung><Erwartetes Ergebnis>
z.B. "GetPerson_WithPersonName_ReturnsPerson"

ad 5) DRY lässt sich bei Unittests imho. nicht komplett vermeiden.

ad 6) Hier wird empfohlen, sich an die "AAA" (arrante, act, assert) syntax zu halten. Das ist bei deinen Tests der Fall. Ev. könnte man noch Variablennamen "expected" und "actual" verwenden, dann kann man im Assert angeben: Assert.That(actual, EqualsTo(expected));

fg
hannes

Dankeschön.

Eine Frage hatte ich beim Start Posting vergessen.

Wie ist das mit den Namespaces? Wenn VS mit Dateien erstellt, werden alle Tests in den Root gepackt, wäre es nicht praktischer wenn die Test Dateien in den selben Namespaces liegen?

Wenn VS mit Dateien erstellt, werden alle Tests in den Root gepackt,[...]

Kann man so (allgemein) nicht sagen. VS benutzt erstens den Namespace, der als Standardnamespace im Projekt angegeben ist. Wenn die Dateien in Ordnern liegen, werden die Ordnernamen mit an den Standardnamespace angehangen.

Genau das hatt VS ja nicht gemacht.
Alle Dateien sind in verschiedenen Namespaces eingeteilt, und das auch verschieden tief, aber das durch den Wizard erstellte Test Projekt hat alle Test Dateien im Root, und auch nur das Root Namespace des Projektes wurde verwendet -.-

Ich habe ein Test Projekt gemacht, da ist es so (Namespaces sind auch gleichzeitig Verzeichnisse)

<Pfad> (<Namespace>)

Demo.csproj
Demo\App.xaml.cs (Demo)
Demo\MainWindow.xaml.cs (Demo)
Demo\Properties\AssemblyInfo.cs (-)
Demo\Properties\Resources.Designer.cs (Demo.Properties)
Demo\Properties\Settings.Designer.cs (Demo.Properties)
Demo\SubFolder\SubFolderClass.cs (Demo.SubFolder)

Nun sag ich VS das er über den Wizard ein UnitTest projekt erstellen soll, VS erstellt dann folgendes:

Demo.Test.csproj
Demo.Test\AppTest.cs (Demo.Test)
Demo.Test\MainWindowTest.cs (Demo.Test)
Demo.Test\Properties\AssemblyInfo.cs (-)
Demo.Test\ResourcesTest.cs (Demo.Test)
Demo.Test\SettingsTest.cs (Demo.Test)
Demo.Test\SubFolderClassTest.cs (Demo.Test)

Man sieht das SubFolderClassTest nicht nur im falschen Verzeichnis liegt sondern auch ein falschen Namespace hat.

Bisher ist es so, das sobald ich ein neuen UnitTest erstelle, ich ihn in den Namespaces noch verschieben und anpassen muss.

Ich arbeite mich derzeit durch das TDD Buch von Kent Beck, dort schreibt er in Java.

Zwei fragen tun sich mir derzeit auf.

Ich habe immer gern pro test Klasse eine Datei.
Nun ist es so gewesen das man eine Klasse
Dollar hatte, also schrieb ich zuerst DollarTest.
Dann kam noch Franc dazu, also schrieb ich FrancTest.
Soweit so gut. Im zuge des TDD sind die Klassen mit der Zeit obsolet geworden und in einer Money aus gelagert.

Worauf ich hinaus will, zwischendurch kam es immer wieder dazu das ich nicht genau wusste wo ich die Methoden hin pack. Gibt es da ein Guten workaround?

Zb test von Equals, da schrieb er

public void TestEquality()
{
    Assert.IsTrue(new Dollar(5), new Dollar(5));
    Assert.IsFalse(new Dollar(5), new Dollar(6));
    Assert.IsTrue(new Franc(5), new Franc(5));
    Assert.IsFalse(new Franc(5), new Franc(6));
    Assert.IsFalse(new Dollar(5), new Franc(5));
}

die ersten beiden kommen in Dollar, die nächsten beiden in Franc, und das letzte? Wohin gehört das?
Es ist also zu dieser Zeit vom vorteil wenn man nur eine Test klasse hat und die Methoden erst korrekt verschiebt wenn die Klassen "fertig" sind. Oder?

Das nächste ist die Namensgebung:
TestMultiplication, TestEqualify, TestPlus
So nennt Kent die Test klassen für JUnit

Wie sollte ich das korrekt benennen?
Multiplication_MultipliesCorrect finde ich da etwas sinn befreit...

TDD Buch von Kent Beck

Hier mal die "Meinung" von Roy Osherove (Buch "The Art of Unit Testing", mit Beispielen in .NET):

Zb test von Equals, da schrieb er[...]

Ein schlechter Test, da mehrere Asserts enthalten sind. Pro Test ein Assert.
Problem ist, dass nach dem ersten fehlgeschlagenen Assert aufgehört wird. D.h. du weißt nicht, ob die folgenden Asserts erfolgreich sind oder nicht und kannst somit den Fehler nicht genau/schnell genug eingrenzen.

Wie sollte ich das korrekt benennen? Multiplication_MultipliesCorrect finde ich da etwas sinn befreit...

Wenn du die Naming standards for unit tests - ISerializable - Roy Osherove's Blog berücksichtigst: [MethodName_StateUnderTest_ExpectedBehavior]. Also für das Multiplikationsbeispiel dann "Multiplication_MultiplyTwoAndTwo_ReturnsFour". Wenn du Probleme hast, deine Testmethoden nach diesem Schema zu benennen, testet die Methode zu viel.

Gruß,
dN!3L

TDD Buch von Kent Beck
Hier mal die "Meinung" von Roy Osherove (Buch "The Art of Unit Testing":::

Hast du den Link vergessen?
Ich finde das Buch bisher sehr gut, läßt sich schön lesen und er führt auch Langsam in TDD ein und wird hin und wieder mal schneller. Ist auch nicht staub trocken.

Zb test von Equals, da schrieb er[...]
Ein schlechter Test, da mehrere Asserts enthalten sind. Pro Test ein Assert.
Problem ist, dass nach dem ersten fehlgeschlagenen Assert aufgehört wird. D.h. du weißt nicht, ob die folgenden Asserts erfolgreich sind oder nicht und kannst somit den Fehler nicht genau/schnell genug eingrenzen.

Stimmt schon, ändert aber nichts an der frage wo

public void Equals_DollarFrance_NotEquals()
{
    Assert.IsFalse(new Dollar(5), new Franc(5));
}

hin gehört, also in welche Klasse.

Das mit der Namensgebung, hmm, muss ich mal überdenken. Der Titel in den Beispiel gerade eben "Equals_DollarFrance_NotEquals" finde ich ziemlich hässlich, vor allem wenn man eine Methode hat die ein Komplexes Objekt zurück gibt und die Verifizierung nicht so einfach ist.

Nehmen wir mal das

public void Create_ValidSessionGenerated()
{
    SessionCreator creator = new SessionCreator();
    ISession session = creator.Create("myName", "myPassword");

    Assert.IsNotNull(session);
    Assert.IsTrue(session.IsLoggedIn);
    Assert.IsFalse(session.IsReadOnly);
    Assert.AreEqual(session.UserName, "My User Name");
}

Es kann auch eine Exception fliegen.

Wie will man das vernünftig abdecken ohne mehreren Asserts und besseren namen?

PS. Wer is Roy Osherove und warum kann der "Standards" definieren?

Hast du den Link vergessen?

Nö, eigentlich nicht. Aber wenn du eine willst: The Art Of Unit Testing - By Roy Osherove - Official Book Site

Das mit der Namensgebung, hmm, muss ich mal überdenken. Der Titel in den Beispiel gerade eben "Equals_DollarFrance_NotEquals" finde ich ziemlich hässlich

[...]ändert aber nichts an der frage wo [...] hin gehört, also in welche Klasse.

Das Assert finde ich etwas seltsam. Was wird denn genau getestet, also was macht Assert.Equals genau? Dollar und France haben beide eine Equals-Methode. Welche wird denn aufgerufen? Eine von beiden? Eine bestimmte? Beide? Oder ist es gar ein ReferenceEquals?
Ich würde so trennen (wenn dieser Test denn Sinn macht): Dollar hat eine Equals-Methode, die wird in DollarTests getestet, France hat eine Equals-Methode, die wird in FranceTests getestet.

Dann schließt sich auch etwas der Kreis zu Namensgebung:

"Equals_DollarFrance_NotEquals" finde ich ziemlich hässlich

In DollarTests hast du "Equals_ToFrance_ReturnsFalse", in FranceTests hast du "Equals_ToDollar_ReturnsFalse".

Nehmen wir mal das [...] Wie will man das vernünftig abdecken ohne mehreren Asserts und besseren namen?

Ein Test testet doch einen bestimmten Aspekt der Spezifikation. In deinem Beispiel testest du aber mehrere verschiedene Aspekte (Factory-Methode gibt was zurück, Session sagt "ist eingeloggt", Session ist nur lesbar, Benutzername ist der mit dem eingeloggt wurde, Exeptions). Alles unterschiedliche Aspekte. Also bitte für jeden einzelnen einen separaten Test erstellen.
Oder mit R. Osheroves Worten: "Wenn du Probleme hast, einen Test zu benennen, testet er zu viel".

Wer is Roy Osherove und warum kann der "Standards" definieren?

Ich wollte nur drauf hinaus, dass es durchaus mehrere Ansätze gibt, von denen sich manche besser und manche schlechter eignen.

Gruß,
dN!3L

public void Create_ValidSessionGenerated()  
{  
    SessionCreator creator = new SessionCreator();  
    ISession session = creator.Create("myName", "myPassword");  
  
    Assert.IsNotNull(session);  
    Assert.IsTrue(session.IsLoggedIn);  
    Assert.IsFalse(session.IsReadOnly);  
    Assert.AreEqual(session.UserName, "My User Name");  
}  

Hi,

genau solche Konstrukte hatte ich auch schon sehr häufig. Mittlerweile bin ich mir jedoch sicher, dass es sinnvoll ist dafür mehrere Tests zu schreiben.
Das Argument "wenn das erste Assert fehlschlägt, dann werden die restlichen nicht mehr evaluiert" finde ich hier als Begründung unsinnig. Dann behebe ich halt den Fehler und schaue, ob danach die folgenden Asserts korrekt sind.

Weshalb ich genau von solchen Mehrfachtests Abstand nehme ist also ein anderer Grund: Was passiert, wenn ich die Session z.B. dahingehend erweitere, dass ein aktueller Timestamp gesetzt wird? Weiterhin ist eine Session nur gültig, wenn dieser Timestamp nicht älter als x Minuten ist.
Das kann ich dann auch per Unit-Test überprüfen. Nur wer garantiert mir, dass der Implementierende nicht einen neuen Test schreibt und dabei nicht vergisst den obigen mit Erwartung "ValidSessionGenerated" auch anzupassen? Unter Umständen habe ich dann einen Test, der grün ist, aber nur weil die Validität gar nicht mehr vollständig überprüft wird.

Ich vermeide mittlerweile Testnamen, die Worte wie "valid" enthalten. Was heute noch "valid" ist, ist es morgen vielleicht schon nicht mehr. Deshalb lieber nur noch harte Fakten testen... und das geht meistens nur mit einem Assert pro Test.

Grüsse,

N1ls

Das Argument "wenn das erste Assert fehlschlägt, dann werden die restlichen nicht mehr evaluiert" finde ich hier als Begründung unsinnig. Dann behebe ich halt den Fehler und schaue, ob danach die folgenden Asserts korrekt sind.

Genau da ist ja der Knackpunkt: Wie finde und behebe ich den Fehler? Neben der Tatsache, dass man in diesem Test - so, wie er da steht - überhaupt gar nicht gleich sieht, warum der Test überhaupt fehlgeschlagen ist - ist es doch viel hilfreicher zu wissen, ob bsp. nur das LoggedIn-Property "kauputt" ist und der Rest noch funktioniert, oder ob ein komplett anderes Session-Objekt geliefert wurde.

dN!3L

Damit habe ich noch nie Probleme gehabt. Sobald ein Test fehlschlägt, setze ich einen Breakpoint auf die erste Zeile meiner Asserts. Da habe ich im VS dann ja sofort das komplette Objekt auf das ich Teste vollständig unter der Lupe und kann eben die nachfolgenden Asserts am "lebendigen Objekt" auf einen Blick prüfen.

Ich sage nicht, dass das gut ist, aber es wäre halt für mich kein Argument gewesen diese Asserts auf mehrere Tests zu verteilen.

N1ls

Dh ich müsste diese Methode:

public void Create_ValidSessionGenerated()
{
    SessionCreator creator = new SessionCreator();
    ISession session = creator.Create("myName", "myPassword");

    Assert.IsNotNull(session);
    Assert.IsTrue(session.IsLoggedIn);
    Assert.IsFalse(session.IsReadOnly);
    Assert.AreEqual(session.UserName, "My User Name");
}

aufdröseln zu:

public void Create_UserNameAndPassword_ReturnsSession()
{
    SessionCreator creator = new SessionCreator();
    ISession session = creator.Create("myName", "myPassword");
    Assert.IsNotNull(session);
}
public void Create_UserNameAndPassword_ReturnsLoggedInSession()
{
    SessionCreator creator = new SessionCreator();
    ISession session = creator.Create("myName", "myPassword");

    Assert.IsTrue(session.IsLoggedIn);
}
public void Create_UserNameAndPassword_ReturnsWritableSession()
{
    SessionCreator creator = new SessionCreator();
    ISession session = creator.Create("myName", "myPassword");

    Assert.IsFalse(session.IsReadOnly);
}
public void Create_UserNameAndPassword_ReturnsMyUserName()
{
    SessionCreator creator = new SessionCreator();
    ISession session = creator.Create("myName", "myPassword");

    Assert.AreEqual(session.UserName, "My User Name");
}

??

Dh ich müsste diese Methode:
...

aufdröseln zu:

...

??

Jein.

  1. Ja, ich würde dafür mehrere Tests schreiben.

  2. Nein, ich würde (mittlerweile) keinen Test mehr schreiben, der nur überprüft, ob ich überhaupt eine Instanz zurück bekomme. Falls nicht, merke ich das sowieso. Dann schlagen mehrere andere Tests fehl und zwar mit einer NullReferenceException. Ich versuche eigentlich nur noch Verhalten zu testen. Gerade beim Test "Create_UserNameAndPassword_ReturnsSession" ist gar nicht mehr klar, ob die Session oder der SessionCreator einen Fehler hat.

  3. Nein, mein Code würde nicht so aussehen. Ich würde das Setup, was in jedem Test gleich ist, in eine Methode auslagern. Wenn sich da mal was ändert, muss man es nur an einer Stelle machen.

  4. Nein, ich würde die Methoden nicht so benennen. "Create_UserNameAndPassword_ReturnsLoggedInSession" passt nicht, besser "Create_ValidCredentials_ReturnLoggedInSession".

Ich glaube Haarspalterei kann bei Unit-Tests und insbesondere deren Benamung eine Tugend sein. Der Name eines Tests muss zwingend und möglichst genau dessen Inhalt widerspiegeln. Und der Inhalt ist mehr oder weniger eine Verhlaltensspezifikation. Und dabei ist ein "IsValid" als Erwartungshaltung immer sehr schwammig... daher lieber mehr und konkretere Tests.

Vielleicht noch einmal ergänzend: Mehrere Asserts innerhalb eines Tests finde ich nicht grundsätzlich schlimm. Wenn ich auf zu einer leeren Liste 1 Element hinzufüge, gehe ich davon aus, dass

a.) der Stack genau 1 Element umfasst
b.) genau das Element auf dem Stack liegt, dass ich hinzugefügt habe

Das in 2 Tests aufzuspalten halte ich für unnötig. Zumal der zweite Test eh fehlschlagen wird bzw. nur wenig aufschlussreich ist.

Die Frage ist dabei also, wie sehr die einzelnen Annahmen miteinander gekoppelt sind.

MfG,

N1ls

Ich kann N1ls eigentlich voll und ganz zustimmen.

Auch in Tests kannst (und solltest) du refaktorisieren. Denn DRY sieht das ja nicht gerade aus. Also z.B. entweder eine Factory-Methode für eine valide Session oder im SetUp eine Session erstellen. Wichtig ist ja auch, dass deine Tests wartbar sind (und bleiben).

Und noch eine Ergänzung zu mehreren Asserts in einem Test: Den folgenden Test mit drei Asserts...


public void SumTests()
{
   Assert.AreEqual(Add(2,2),4);
   Assert.AreEqual(Add(2,0),2);
   Assert.AreEqual(Add(2,-1),1);
}

... kann man auch - durch parametrisierte Tests - in (quasi) drei Tests mit jeweils einem Assert umwandeln:


[RowTest]
[Row(2,2,4)]
[Row(2,0,2)]
[Row(2,-1,1)]
public void SumTests(int a,int b,int expected)
{
   Assert.AreEqual(Add(a,b),expected);
}

Gruß,
dN!3L

Bzgl DRY hatte ich ja schon einmal gefragt, siehe mein start Posting punkt 5.

Nach Beachtung der jetzigen Vorschläge wäre der Code folgender:

Create_UserNameAndPassword_ReturnsSession kommt komplett weg, das Argument das die anderen dann eh fehl schlagen und Create auch abgedeckt sind stimmt absolut 😃

private ISession CreateSession()
{
    SessionCreator creator = new SessionCreator();
    return creator.Create("myName", "myPassword");
}

public void Create_ValidCredentials_ReturnsLoggedInSession()
{
    ISession session = CreateSession();
    Assert.IsTrue(session.IsLoggedIn);
}

public void Create_ValidCredentials_ReturnsWritableSession()
{
    ISession session = CreateSession();
    Assert.IsFalse(session.IsReadOnly);
}

public void Create_ValidCredentials_ReturnsMyUserName()
{
    ISession session = CreateSession();
    Assert.AreEqual(session.UserName, "My User Name");
}

user name und passwort könnte auch mit übergeben werden, da es aber immer das selbe ist hab ich es weg gelassen.
Was meint ihr?

@dN!3L
Was ist das für eine eigenartige Syntax
[RowTest]
[Row(2,2,4)]
[Row(2,0,2)]
[Row(2,-1,1)]
?

//Dazu, Google flüsterte mir das es aus NUnit kommt, ich verwende aber MSTest von VS, aber das spielt hier jetzt keine rollen.

Hallo dN!3L,

bietet RowTest irgendwelche Vorteile gg. TestCase?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

user name und passwort könnte auch mit übergeben werden, da es aber immer das selbe ist hab ich es weg gelassen.
Was meint ihr?

Ich würde es auch nicht übergeben. Jedoch würde ich wahrscheinlich die Methode eher "CreateSessionUsingValidCredentials" nennen. Das wäre eine klare Abgrenzung zu einer evtl. später notwendigen Methode "CreateSessionUsingNotExistingUsername" oder "CreateSessionUsingWrongPassword". Ob man diese braucht, hängt natürlich vom Verhalten des System under Test und den weiteren Tests ab. Man gewinnt aber zumindestens an Klarheit.

Ansonsten: Genau so finde ich die Tests sehr gut lesbar. Besser als mit mehreren Asserts. Und ich habe den Vorteil, dass jetzt alles im Testprotokoll eindeutig festgehalten wird. Man braucht den Testcode nicht mehr. Im Protokoll der ursprünglichen Version sehe ich nur sowas wie

Create_ValidSessionGenerated - Passed

jetzt dagegen:

Create_ValidCredentials_ReturnsWritableSession - Passed
Create_ValidCredentials_ReturnsLoggedInSession - Passed
Create_ValidCredentials_ReturnsMyUserName - Passed

Finde ich zumindest elegant 😉

MfG,

N1ls

Stimmt, da hast du recht.

Mir ist schon klar das mehrere Asserts nicht "böse" sind, mir fehlt aber noch das Gefühl wann mehrere Asserts benutzt werden könnten oder ich es lieber aufteilen sollte.

Bzgl NUnit, Ich hatte bisher nicht das Bedürfnis NUnit zu verwenden, wo doch in VS 2010 MsTest richtig gut integriert ist (zudem brauch ich dann keine 3rdParty lib).

Hallo,

bzgl. NUnit vs. MsTest: NUnit wins for Unit Testing. Hinzu kommt dass NUnit für jede Version von Visual Studio und ohne VS verwendet werden kann.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Da die Tests immer in VS ausgeführt werden und sogar beim entwickeln hilft, sehe ich kein Anlass etwas zu wechseln.

Nehmen wir einfach TDD.
Beim entwickeln der app brauch ich eine Klasse "Bank", nach TDD erstelle ich eine BankTest klasse, gebe dort Bank bank = new Bank(); ein, und mit CTRL+. Kann ich mir direkt eine Bank erstellen lassen.
Nun brauch ich die Rate USD->EUR, dann tipp ich einfach
Rate rate = bank.GetRate("USD", "EUR"); und kann mir direkt mit shortcut die Methode GetRate und die klasse Rate erstellen lassen.
Sobald die Methode GetRate erstellt ist, CTRL+Klick auf den namen, und schon kann ich dort die erste Fake Implementation einhacken um den Test passen zu lassen.

Siehe z.b. hier: Test-Driven Development with Visual Studio 2010

Die super Integration in VS macht es ziemlich handlich.
Kann die Tests auch in Test listen verwalten und einzelne Listen aufrufen lassen, wenn was failed kann ich direkt dort hin springen, usw usf.

Scheint auch mehr ne Glaubensfrage zu sein 😄
Stop the war between NUnit and MSTest : Make them friends

Aber wie gesagt, ich sehe kein Anlass zu wechseln, ich finde es gut das ich mit VSTS alles in einen habe welches such super zusammen arbeitet 😃

Hallo zusammen,

Scheint auch mehr ne Glaubensfrage zu sein 😄

für Glaubensfragen haben wir schon Tatsächlicher Nutzen von Unit-Tests. Konzentriert euch hier bitte auf das eigentliche Thema: "UnitTests: Strukturierung und Bennenung der Tests/Testprojekte/Testmethoden".

herbivore

Ok zurück zum Thema

Bzgl mein:
"Mir ist schon klar das mehrere Asserts nicht "böse" sind, mir fehlt aber noch das Gefühl wann mehrere Asserts benutzt werden könnten oder ich es lieber aufteilen sollte."

Gibt es eventuell ein paar Tipps und Tricks wie man raus findet ob mehrere Asserts oder nicht?

Hallo CSL,

ich würds wie weiter oben geschrieben halten:

Zitat von: dN!3L
Oder mit R. Osheroves Worten: "Wenn du Probleme hast, einen Test zu benennen, testet er zu viel".

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Zu den parametrisierten Tests:

Was ist das für eine eigenartige Syntax?

Da ich davon ausgehe, dass du weißt, was Attribute sind:

bietet RowTest irgendwelche Vorteile gg. TestCase?

AFAIK kommt RowTest ursprünglich aus MbUnit. Da es mit NUnit nicht möglich war, parametrisierte Tests zu erstellen (da Testmethoden parameterlos sein mussten), wurde das RowTestAttribute als Erweiterung (für NUnit 2.4) übernommen. In NUnit 2.5 kam dann das TestCaseAttribute dazu.

Hast du den Link vergessen?

Einen habe ich gerade noch gesehen: Manning: The Art of Unit Testing. Da gibt's den ganzen SampleCode und Probekapitel vom Buch.

Gruß,
dN!3L

Hallo CSL,

ich würds wie weiter oben geschrieben halten:

Zitat von: dN!3L
Oder mit R. Osheroves Worten: "Wenn du Probleme hast, einen Test zu benennen, testet er zu viel".

Das ist sicherlich ein gutes Kriterium, alleine aber zu wenig. Gerade bei Anfängern in Sachen Unit-Tests merke ich häufig, dass diese überhaupt keine Probleme haben einen Namen für einen Test zu finden. Beispielsweise finde ich da bei einem Stack Testmethoden a la "TestPushElement". Das sieht für einen Anfänger total unproblematisch und nach einem guten Namen aus. Und innerhalb solcher Tests wird dann natürlich auf alles, was sich bewegt, mit Asserts geschossen.

Voraussetzung ist also m.E. nach erstmal, dass es Namenskonventionen für Tests gibt. Darin muss sich inhaltlich wiederspiegeln, was getan wird und was erwartet wird. Erst dann lässt sich obiges Kriterium sinnvoll anwenden.

Aber es ist irgendwie immer noch nicht ausreichend. Bleiben wir mal beim Stack:


public Push_OneElement_ElementIsTopElementOnTheStack()
{
    // Arrange
    var sut = new Stack<int>();
    
    // Act
    sut.Push(2);

    // Assert
    Assert.AreEqual(1, sut.Count);
    Assert.AreEqual(2, sut.Peek());
}

So (ähnlich) hätte ich das bis vor kurzem als Test implementiert. Ich hatte auch kein Problem damit, einen Namen für den Test zu finden. Dennoch würde ich heute sagen, es wird eindeutig zuviel getestet.
Was würde jetzt passieren, wenn im Code jedes Element versehentlich doppelt auf dem Stack landet? Der Test würde fehlschlagen mit der Meldung, dass 1 erwartet wurde, jedoch der tatsächliche Wert 2 ist. Hier wird schon deutlich, dass ich mir die optionalen Meldungen der Asserts nicht mehr sparen kann, wenn ich mehrere innerhalb eines Tests habe.
Viel schlimmer aber noch ist, dass ich hier im Testprotokoll quasi belogen werde. Wenn der Test fehlschlägt, heisst das die Erwartung "ElementIsTopElementOnTheStack" ist nicht erfüllt. Stimmt aber nicht, das oberste Element entspricht nämlich dem hinzugefügten Element.

Vielleicht ist das nicht für jeden (sofort) nachvollziehbar, aber ich fühle mich da mittlerweile mit einem extra Test "Push_OneElement_ElementCountIncreasesByOne" bedeutend besser.
Und beim Durchschauen meiner Tests geht mir das irgendwie bei jedem Test so, der mehr als ein Assert enthält. Entweder komme ich zu dem Schluss, die getesteten Dinge sind zu unterschiedlich, um in einen Test zu gehören(hier die Größe des Stacks und der Inhalt des Stacks), oder ich bin nicht damit zufrieden, wie sich das im Protokoll der Testausführung liest.

Ich bin immer noch nicht an dem Punkt, dass ich mehr als ein Assert per se verteufeln würde. Aber da müssen schon wirklich gute Gründe sprechen. Wäre schön, wenn hier jemand mal ein Beispiel hätte, wo man partout nicht ohne mehrere Asserts auskommt.

MfG,

N1ls

Wie wär es damit, gerade gefunden in mein TDD Material:

[TestMethod]
public void Plus_FileDollarPlusFiveDollar_ReturnsSumExpression()
{
	Money five = Money.Dollar(5);
	Money six = Money.Dollar(6);

	IExpression result = five.Plus(six);
	Sum sum = (Sum)result;

	Assert.AreEqual(five, sum.Augend);
	Assert.AreEqual(six, sum.Addend);
}

"Sum" ist eine Expression die die beiden Zahlenwerte enthält, hier wurde überprüft das "Plus" korrekt "Sum" erstellt hat.
Das in zwei Tests wäre schon recht absurd.

Alle anderen Tests haben aber tatsächlich nur ein Assert, laut Buch nicht, habe es aber selber so gemacht.
Habe auch gerade mal die anderen Projekte durch geschaut wo ich Units habe, Mehr als 1 Assert habe ich bisher nicht gefunden, scheint bisher gut zu laufen 😁

Dazu fällt mir etwas ein, habe ich gerade in einem Projekt gesehen.

Ich bekomme von einer Methode eine Liste von Dokumenten, die Dokumente wiederum haben eine Liste von Sprachen.
Ich muss prüfen das die Sprachen alle korrekt sind, also Gültige Sprachen.
Ich hatte zu dem Zweck einfach die Dokumente geloop und in dem loop das Assert per Linq auf die Sprach liste los gelassen.
Wie würde man so etwas korrekt Testen?

Wie wär es damit, gerade gefunden in mein TDD Material:

[TestMethod]  
public void Plus_FileDollarPlusFiveDollar_ReturnsSumExpression()  
{  
  Money five = Money.Dollar(5);  
  Money six = Money.Dollar(6);  
  
  IExpression result = five.Plus(six);  
  Sum sum = (Sum)result;  
  
  Assert.AreEqual(five, sum.Augend);  
  Assert.AreEqual(six, sum.Addend);  
}  

"Sum" ist eine Expression die die beiden Zahlenwerte enthält, hier wurde überprüft das "Plus" korrekt "Sum" erstellt hat.
Das in zwei Tests wäre schon recht absurd.

Das wäre schonmal ein Beispiel, wo der Testname nicht passt("Plus_FiveDollarPlusSixDollar_ReturnsSumExpression" <- mal die vertipper direkt bereinigt). Ein Test für diesen Testnamen müsste ja eigentlich folgendermassen aussehen:


public void Plus_FiveDollarPlusSixDollar_ReturnsSumExpression()
{
	Money five = Money.Dollar(5);
	Money six = Money.Dollar(6);

	IExpression result = five.Plus(six);

	Assert.IsTrue(result is Sum);
}

Das finde ich erstens einen guten Test, um sicherzustellen, dass das IExpression, was von der Plus-Methode zurückgeliefert wird, wirklich vom Typ Sum ist und zweitens prüft der Test nun genau das, was in seinem Namen steckt.

Aber wie weiter? Kommutativgesetz lassen wir mal aussen vor. Nehmen wir an, die Reihenfolge der Summanden wäre wichtig. Dann käme ich zu dem Testnamen "Plus_FiveDollarPlusSixDollar_ReturnsSumWithAugendFiveAndAddendSix". Das "And" macht mir da wieder Sorgen. Was würde es bedeuten, dies wirklich wieder auf 2 Tests aufzusplitten?


private Sum GetSumAddFiveDollarAndSixDollar()
{
	Money five = Money.Dollar(5);
	Money six = Money.Dollar(6);

	return (Sum)(five.Plus(six));
}

public void Plus_FiveDollarPlusSixDollar_SumWithAugendFive()
{
        Sum s = GetSumAddFiveDollarAndSixDollar();

        Assert.AreEqual(5, s.Augend);
}

public void Plus_FiveDollarPlusSixDollar_SumWithAddendSix()
{
        Sum s = GetSumAddFiveDollarAndSixDollar();

        Assert.AreEqual(5, s.Addend);
}

Jetzt wird eigentlich auch klar, dass die Prüfung auf ein Sum-Objekt bei der Plus-Methode gar nicht mehr notwendig ist(man könnte sie dennoch beibehalten), dies wird implizit mitgeprüft.

Ist das jetzt wirklich absurd, daraus 2 Testcases zu machen? Ich prüfe auf jeden Fall auch zwei unterschiedliche Dinge: Die Werte und deren Reihenfolge.

Dazu fällt mir etwas ein, habe ich gerade in einem Projekt gesehen.

Ich bekomme von einer Methode eine Liste von Dokumenten, die Dokumente wiederum haben eine Liste von Sprachen.
Ich muss prüfen das die Sprachen alle korrekt sind, also Gültige Sprachen.
Ich hatte zu dem Zweck einfach die Dokumente geloop und in dem loop das Assert per Linq auf die Sprach liste los gelassen.
Wie würde man so etwas korrekt Testen?

Dann bewegen wir uns schon gar nicht mehr im Bereich von Unit-Tests. Das sind Integrationstests. 😉

MfG,

N1ls

Das wäre schonmal ein Beispiel, wo der Testname nicht passt

Jup, weil ich den Code ein klein wenig angepasst hatte bevor ich ihn hier Postete, ursprünglich gav es die six variable nicht sondern five wurde doppelt verwendet, hatte vergessen den titel zu ändern.

Dann bewegen wir uns schon gar nicht mehr im Bereich von Unit-Tests. Das sind Integrationstests. 😉

Ah, siehste, ich wollte schon lange mal nach lesen wo da der unterschied ist 😁

Ansonsten kann ich deiner Argumentation komplett folgen und stimme zu, danke dir 🙂