Laden...

Wie auf Testing-Methoden-Ebene angeben, dass ich Seeding brauche (oder nicht)?

Erstellt von Davaaron vor 3 Jahren Letzter Beitrag vor 3 Jahren 639 Views
D
Davaaron Themenstarter:in
106 Beiträge seit 2016
vor 3 Jahren
Wie auf Testing-Methoden-Ebene angeben, dass ich Seeding brauche (oder nicht)?

Hi,

im Wesentlichen geht es darum, dass ich in meinem Projekt ein Datenseeding auf Methodenebene anstrebe. Aktuell verwende ich XUnit zum Testen meiner Services (MVC Service/Repo, die EF Core's DbContext verwenden) und das funktioniert auch ganz gut. Ein klassischer Aufbau eines solchen Tests



public class SpecialServiceTests: BaseServiceTests
{

    public ServiceTests()
    {
         _serviceUnderTest = Resolve<SpecialService>();
    }

    [Fact]
    public async Task CreateMultipleObjects_Retrieve_ShouldNotBeNull()
    {
          //omitted for brevity
    }

    [Fact]
    public async Task DeleteSingleObject_Retrieve_ShouldBeNull()
    {
          //omitted for brevity
    }
}


Jede Testklasse erbt von BaseServiceTest, in dessen Konstruktur die Erstellung des DbContexts und das Datenseeding stattfindet, d.h. jede Methode hat ihre eigene DbContext-Instanz (InMemory) und sind somit voneinander isoliert.
Nun ist es so, dass ich nicht für jede Methode das Seeding benötige, bspw. für die Methode "CreateMultipleObjects". Dies will ich ändern, und zwar möglichst elegant.

Nach langer Überlegung komme ich zu folgendem Schluss: Ich kann nicht explizit sagen, dass für eine bestimmte Methode das Seeding gemacht werden soll. Daher müsste ich generell hingehen und sagen, dass das Seeding überhaupt nicht stattfindet im Konstruktur, sondern am Anfang einer jeden Methode, die das Seeding benötigt.
Also z.B.



    [Fact]
    public async Task CreateMultipleObjects_Retrieve_ShouldNotBeNull()
    {
          //does not need seeding, do your stuff
          Enumerable.Range(1, 10).ForEach(x => ....);
    }

    [Fact]
    public async Task DeleteSingleObject_Retrieve_ShouldBeNull()
    {
          //needs seeding
          SeedData();
          
          //Now do your stuff
    }


Diese Lösung gefällt mir nicht, vor allem weil es bedeutend mehr Testfälle gibt, in denen ich das Seeding brauche. Meine liebste Lösung wäre, einfach mithilfe eines Attributs an einer Methode zu sagen, dass es kein Seeding braucht.

Dafür bräuchte ich aber mehr Kontrolle über das Test-Framework. Soweit ich weiß, weiß XUnit im Konstruktur der Testklasse noch gar nicht, welche Methode ausgeführt wird und eine solche Flexibilität wie ich sie gerne hätte, habe ich auf der XUnit Seite nicht finden können.

Kann mir jemand weiterhelfen? Ich wäre auch bereit, auf ein anderes Test-Framework umzusteigen.

Hinweis:
Das Seeding findet klassisch in C# statt. Später will ich noch die Möglichkeit, das Seeding per ".json" oder ".xml" anzugeben, damit man nicht jedes Mal den Code anpassen muss, wenn sich etwas ändert.

Edit: So etwas in der Art wäre toll


    [Fact]
    [Seed(typeof(MySpecialDbContext), typeof(MySpecialEntity), typeof(MyDependentEntity))]
    //oder einfach [Seed]
    public async Task DeleteSingleObject_Retrieve_ShouldBeNull()
    {
          //Seeding happens by attribute
          
          //Now do your stuff
    }


Damit könnte ich angeben, welcher DbContext mit welchen Entities geseedet werden soll. Dafür bräuchte ich logischen Code, der im Attribut selbst ausgeführt wird. Oder ich gebe einfach ein Flag mit, ob geseedet wird oder nicht.

16.807 Beiträge seit 2008
vor 3 Jahren

XUnit führt Tests auf Methoden-Ebene parallel aus.
Das bedeutet, dass Du keinerlei Daten oder Instanzen Methoden-übergreifend sharen solltest.

Für Test Seeding verwende ich AutoFixture; und kann Dir selbiges empfehlen.

XUnit selbst nennt das ganze nicht Seed sondern Theory Data.
Das bedeutet dass Du bei Theory (nicht Fact) entsprechend externe Testdaten referenzieren kannst; sowohl Instanzen wie auch Quellen (Dateien, Datenbanken...).