Ist die Verwendung von Komponententests zum Erzählen einer Geschichte eine gute Idee?

13

Ich habe also ein Authentifizierungsmodul, das ich vor einiger Zeit geschrieben habe. Jetzt sehe ich die Fehler auf meinem Weg und schreibe Unit-Tests dafür. Während ich Unit-Tests schreibe, fällt es mir schwer, gute Namen und gute Testbereiche zu finden. Zum Beispiel habe ich Dinge wie

  • RequiresLogin_should_redirect_when_not_logged_in
  • RequiresLogin_should_pass_through_when_logged_in
  • Login_soll_arbeiten_wenn_proper_credentials_gegeben werden

Persönlich finde ich es ein bisschen hässlich, obwohl es "richtig" zu sein scheint. Ich habe auch Probleme, zwischen Tests zu unterscheiden, indem ich sie nur scanne (ich muss den Methodennamen mindestens zweimal lesen, um zu wissen, was gerade fehlgeschlagen ist).

Also dachte ich mir, anstatt Tests zu schreiben, die nur die Funktionalität testen, schreiben wir vielleicht eine Reihe von Tests, die Szenarien abdecken.

Dies ist zum Beispiel ein Teststub, den ich mir ausgedacht habe:

public class Authentication_Bill
{
    public void Bill_has_no_account() 
    { //assert username "bill" not in UserStore
    }
    public void Bill_attempts_to_post_comment_but_is_redirected_to_login()
    { //Calls RequiredLogin and should redirect to login page
    }
    public void Bill_creates_account()
    { //pretend the login page doubled as registration and he made an account. Add the account here
    }
    public void Bill_logs_in_with_new_account()
    { //Login("bill", "password"). Assert not redirected to login page
    }
    public void Bill_can_now_post_comment()
    { //Calls RequiredLogin, but should not kill request or redirect to login page
    }
}

Ist das ein gehörtes Muster? Ich habe Akzeptanzgeschichten und ähnliches gesehen, aber das ist grundlegend anders. Der große Unterschied besteht darin, dass ich mir Szenarien überlege, um die Tests zu "erzwingen". Anstatt manuell zu versuchen, mögliche Interaktionen zu finden, die ich testen muss. Ich weiß auch, dass dies Unit-Tests fördert, die nicht genau eine Methode und Klasse testen. Ich finde das aber OK. Ich bin mir auch bewusst, dass dies zumindest für einige Test-Frameworks Probleme verursachen wird, da diese normalerweise davon ausgehen, dass die Tests unabhängig voneinander sind und die Reihenfolge keine Rolle spielt (wo dies in diesem Fall der Fall wäre).

Wie auch immer, ist das überhaupt ein ratsames Muster? Oder wäre dies eine perfekte Lösung für Integrationstests meiner API und nicht für "Unit" -Tests? Dies ist nur ein persönliches Projekt, daher bin ich offen für Experimente, die gut oder schlecht laufen können.

Earlz
quelle
4
Die Linien zwischen Einheit, Integration und Funktionstests sind verschwommen, wenn ich würde muß einen Namen für den Test Stummel holt, würde es funktionieren.
Yannis
Ich denke, es ist Geschmackssache. Persönlich verwende ich den Namen von allem, was ich teste, mit _testangehängten und benutze Kommentare, um zu notieren, welche Ergebnisse ich erwarte. Wenn es sich um ein persönliches Projekt handelt, finden Sie einen Stil, mit dem Sie sich wohl fühlen, und bleiben Sie dabei.
Mr Lister
1
Ich habe eine Antwort mit Details zu einer traditionelleren Art und Weise des Schreibens Unit - Tests das Muster anordnen / Act / Assert mit geschrieben, aber ein Freund hat viel Erfolg mit hatte github.com/cucumber/cucumber/wiki/Gherkin , das ist verwendet für specs und afaik kann gurkentests generieren.
StuperUser
Obwohl ich die Methode, die Sie mit nunit oder ähnlichem gezeigt haben, nicht verwenden würde, unterstützt nspec den Aufbau von Kontext und das Testen in einer eher geschichtenorientierten Art: nspec.org
Mike
1
ändere "Bill" in "User" und du bist fertig
Steven A. Lowe

Antworten:

15

Ja, es ist eine gute Idee, Ihren Tests Namen der Beispielszenarien zu geben, die Sie testen. Und wenn Sie Ihr Unit-Test-Tool nicht nur für Unit-Tests verwenden, ist das vielleicht auch in Ordnung. Viele Leute tun dies mit Erfolg (auch ich).

Aber nein, es ist definitiv keine gute Idee, Ihre Tests in einer Weise zu schreiben, in der die Reihenfolge der Ausführung der Tests von Bedeutung ist. Mit NUnit kann der Benutzer beispielsweise interaktiv auswählen, welcher Test ausgeführt werden soll, sodass dies nicht mehr wie beabsichtigt funktioniert.

Sie können dies hier leicht vermeiden, indem Sie den Haupttestteil jedes Tests (einschließlich der "Bestätigung") von den Teilen trennen, die Ihr System in den richtigen Anfangszustand versetzen. Verwenden Sie das obige Beispiel: Schreiben Sie Methoden, um ein Konto zu erstellen, sich anzumelden und einen Kommentar zu veröffentlichen - ohne Behauptung. Verwenden Sie diese Methoden dann in verschiedenen Tests erneut. Sie müssen der [Setup]Methode Ihrer Testgeräte auch Code hinzufügen , um sicherzustellen, dass sich das System in einem ordnungsgemäß definierten Ausgangszustand befindet (z. B. noch keine Konten in der Datenbank, noch keine Verbindungen usw.).

BEARBEITEN: Natürlich scheint dies der "Story" -Natur Ihrer Tests zu widersprechen, aber wenn Sie Ihren Hilfsmethoden aussagekräftige Namen geben, finden Sie Ihre Storys in jedem Test.

Also sollte es so aussehen:

[TestFixture]
public class Authentication_Bill
{
    [Setup]
    public void Init()
    {  // bring the system in a predefined state, with noone logged in so far
    }

    [Test]
    public void Test_if_Bill_can_create_account()
    {
         CreateAccountForBill();
         // assert that the account was created properly 
    }

    [Test]
    public void Test_if_Bill_can_post_comment_after_login()
    { 
         // here is the "story" now
         CreateAccountForBill();
         LoginWithBillsAccount();
         AddCommentForBill();
        //  assert that the right things happened
    }

    private void CreateAccountForBill()
    {
        // ...
    }
    // ...
}
Doc Brown
quelle
Ich würde noch weiter gehen und sagen, dass die Verwendung eines xUnit-Tools zum Ausführen von Funktionstests in Ordnung ist, solange Sie das Tool nicht mit dem Testtyp verwechseln und diese Tests von den tatsächlichen Komponententests getrennt halten, damit Entwickler dies tun können Führen Sie zum Festschreibungszeitpunkt noch schnell Unit-Tests durch. Diese sind wahrscheinlich viel langsamer als Unit-Tests.
BDSL
4

Ein Problem beim Erzählen einer Geschichte mit Komponententests besteht darin, dass nicht explizit festgelegt wird, dass Komponententests völlig unabhängig voneinander angeordnet und ausgeführt werden sollten.

Ein guten Unit - Test sollte vollständig von allen anderen abhängigen Code isoliert werden, es ist die kleinste Einheit des Codes , die getestet werden können.

Dies hat den Vorteil, dass Sie nicht nur den Code bestätigen, sondern auch die Diagnose erhalten, wenn ein Test fehlschlägt, und zwar genau dort, wo der Code falsch ist. Wenn ein Test nicht isoliert ist, müssen Sie sich ansehen, worauf es ankommt, um genau herauszufinden, was schief gelaufen ist, und um einen großen Vorteil des Unit-Tests zu verpassen. Die Reihenfolge der Ausführung kann auch eine Menge falscher Negative auslösen. Wenn ein Test fehlschlägt, können die folgenden Tests fehlschlagen, obwohl der Code, den sie testen, einwandfrei funktioniert.

Ein guter Artikel im Detail ist der Klassiker über schmutzige Hybridtests .

Um die Klassen, Methoden und Ergebnisse lesbar zu machen, verwendet das große Art of Unit-Testen die Namenskonvention

Testklasse:

ClassUnderTestTests

Testmethoden:

MethodUnderTest_Condition_ExpectedResult

Um das Beispiel von @Doc Brown zu kopieren, schreibe ich anstelle von [Setup], das vor jedem Test ausgeführt wird, Hilfsmethoden, um zu testende isolierte Objekte zu erstellen.

[TestFixture]
public class AuthenticationTests
{
    private Authentication GetAuthenticationUnderTest()
    {
        // create an isolated Authentication object ready for test
    }

    [Test]
    public void CreateAccount_WithValidCredentials_CreatesAccount()
    {
         //Arrange
         Authentication codeUnderTest = GetAuthenticationUnderTest();
         //Act
         Account result = codeUnderTest.CreateAccount("some", "valid", "data");
         //Assert
         //some assert
    }

    [Test]
    public void CreateAccount_WithInvalidCredentials_ThrowsException()
    {
         //Arrange
         Authentication codeUnderTest = GetAuthenticationUnderTest();
         Exception result;
         //Act
         try
         {
             codeUnderTest.CreateAccount("some", "invalid", "data");
         }
         catch(Exception e)
         {
             result = e;
         }
         //Assert
         //some assert
    }
}

Die fehlgeschlagenen Tests haben also einen aussagekräftigen Namen, der Ihnen Aufschluss darüber gibt, welche Methode, die Bedingung und das erwartete Ergebnis genau fehlgeschlagen sind.

So habe ich immer Unit-Tests geschrieben, aber ein Freund hat viel Erfolg mit Gerkin .

StuperUser
quelle
1
Obwohl ich denke, dass dies ein guter Beitrag ist, bin ich mir nicht einig darüber, was der verlinkte Artikel über den "Hybrid" -Test sagt. Es kann meiner Meinung nach sehr hilfreich sein, "kleine" Integrationstests zu haben (zusätzlich, natürlich nicht alternativ zu reinen Unit-Tests), auch wenn sie Ihnen nicht genau sagen können, welche Methode den falschen Code enthält. Wenn diese Tests wartbar sind, hängt es davon ab, wie sauber der Code für diese Tests geschrieben wurde, sie sind an sich nicht "schmutzig". Und ich denke, das Ziel dieser Tests kann sehr klar sein (wie im Beispiel des OP).
Doc Brown
3

Was Sie beschreiben, klingt für mich eher nach Behavior Driven Design (BDD) als nach Unit-Tests. Schauen Sie sich SpecFlow an , eine .NET BDD-Technologie, die auf der Gherkin DSL basiert .

Leistungsstarkes Zeug, das jeder Mensch lesen / schreiben kann, ohne etwas über Codierung zu wissen. Unser Testteam setzt es erfolgreich für unsere Integrationstestsuiten ein.

Bezüglich der Konventionen für Unit-Tests scheint die Antwort von @ DocBrown eindeutig zu sein.

Drew Marsh
quelle
Zur Information, BDD ist genau wie TDD, es ist nur der Schreibstil, der sich ändert. Beispiel: TDD = assert(value === expected)BDD = value.should.equals(expected)+ Sie beschreiben die Merkmale in Schichten, die das Problem der Unabhängigkeit von Komponententests lösen. Das ist ein toller Stil!
Offirmo