it-swarm.com.de

Unit Test zum Testen der Erstellung eines Domänenobjekts

Ich habe einen Unit Test, der so aussieht:

[Test]
public void Should_create_person()
{
     Assert.DoesNotThrow(() => new Person(Guid.NewGuid(), new DateTime(1972, 01, 01));
}

Ich behaupte, dass hier ein Personenobjekt erstellt wird, d. H. Dass die Validierung nicht fehlschlägt. Wenn der Guid beispielsweise null ist oder das Geburtsdatum vor dem 01.01.1900 liegt, schlägt die Validierung fehl und eine Ausnahme wird ausgelöst (was bedeutet, dass der Test fehlschlägt).

Der Konstruktor sieht folgendermaßen aus:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

Ist das eine gute Idee für einen Test?

Hinweis : Ich verfolge einen klassizistischen Ansatz zum Unit-Test des Domain-Modells, wenn dies der Fall ist hält ein Lager.

11
w0051977

Dies ist ein gültiger Test (obwohl ziemlich übereifrig) und ich mache ihn manchmal, um die Konstruktorlogik zu testen. Wie Laiv in den Kommentaren erwähnt hat, sollten Sie sich jedoch fragen, warum.

Wenn Ihr Konstruktor so aussieht:

public Person(Guid guid, DateTime dob)
{
  this.Guid = guid;
  this.Dob = dob;
}

Ist es sinnvoll zu testen, ob es wirft? Ob die Parameter richtig zugewiesen sind, kann ich verstehen, aber Ihr Test ist ziemlich übertrieben.

Wenn Ihr Test jedoch ungefähr so ​​funktioniert:

public Person(Guid guid, DateTime dob)
{
  if(guid == default(Guid)) throw new ArgumentException("Guid is invalid");
  if(dob == default(DateTime)) throw new ArgumentException("Dob is invalid");

  this.Guid = guid;
  this.Dob = dob;
}

Dann wird Ihr Test relevanter (da Sie tatsächlich irgendwo im Code Ausnahmen auslösen).

Eine Sache würde ich sagen, im Allgemeinen ist es eine schlechte Praxis, viel Logik in Ihrem Konstruktor zu haben. Die grundlegende Validierung (wie die oben durchgeführten Null-/Standardprüfungen) ist in Ordnung. Aber wenn Sie eine Verbindung zu Datenbanken herstellen und Daten von jemandem laden, riecht Code dort wirklich ...

Wenn Ihr Konstruktor es wert ist, getestet zu werden (weil viel Logik vorhanden ist), stimmt aus diesem Grund möglicherweise etwas anderes nicht.

Sie werden mit ziemlicher Sicherheit andere Tests haben, die diese Klasse in Geschäftslogikebenen abdecken. Konstruktoren und Variablenzuweisungen werden mit ziemlicher Sicherheit eine vollständige Abdeckung durch diese Tests erhalten. Daher ist es möglicherweise sinnlos, spezifische Tests speziell für den Konstruktor hinzuzufügen. Nichts ist jedoch schwarzweiß und ich hätte nichts gegen diese Tests, wenn ich sie mit Code überprüfen würde - aber ich würde fragen, ob sie über die Tests an anderer Stelle in Ihrer Lösung hinaus einen Mehrwert bieten.

In Ihrem Beispiel:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

Sie führen nicht nur eine Validierung durch, sondern rufen auch einen Basiskonstruktor auf. Für mich ist dies ein weiterer Grund für diese Tests, da die Konstruktor-/Validierungslogik jetzt auf zwei Klassen aufgeteilt ist, was die Sichtbarkeit verringert und das Risiko unerwarteter Änderungen erhöht.

TLDR

Diese Tests haben einen gewissen Wert, die Validierungs-/Zuweisungslogik wird jedoch wahrscheinlich von anderen Tests in Ihrer Lösung abgedeckt. Wenn diese Konstruktoren viel Logik enthalten, die erhebliche Tests erfordert, deutet dies darauf hin, dass dort ein unangenehmer Codegeruch lauert.

18
Liath

Hier schon eine gute Antwort, aber ich denke, eine weitere Sache ist erwähnenswert.

Wenn Sie TDD "by the book" ausführen, müssen Sie zuerst einen Test schreiben, der den Konstruktor aufruft, noch bevor der Konstruktor implementiert wird. Dieser Test könnte tatsächlich so aussehen wie der von Ihnen vorgestellte, selbst wenn die Implementierung des Konstruktors keine Validierungslogik enthält.

Beachten Sie auch, dass man für TDD zuerst einen anderen Test wie schreiben sollte

  Assert.Throws<ArgumentException>(() => new Person(Guid.NewGuid(), 
        new DateTime(1572, 01, 01));

vor Hinzufügen der Prüfung für DateTime(1900,01,01) zum Konstruktor.

Im TDD-Kontext ist der gezeigte Test durchaus sinnvoll.

12
Doc Brown