2016-12-27 5 views
0

Ich arbeite daran, meine automatisierten Funktionstests parallel zu optimieren, um die Laufzeit der Testsuite zu verkürzen. Das Problem, das ich habe, ist zu verstehen, wie man meine Testdaten verwaltet, wenn die Tests parallel laufen.So verwalten Sie Testdaten, wenn Tests parallel ausgeführt werden

Genauer gesagt, wenn die Tests nacheinander ausgeführt werden, verwende ich einen einzelnen Benutzer, um sich problemlos bei der Anwendung anzumelden. Dies funktioniert jedoch nicht, wenn Sie mehrere Tests mit diesem Benutzer ausführen und versuchen, sich anzumelden. Ich habe andere Benutzer, mit denen ich mich anmelden kann. Meine Frage ist, wie kann ich diese Benutzer verwalten, so dass 1 Test 1 eindeutigen Benutzer verwendet? Und kein anderer Test berührt diesen Benutzer, bis er frei ist.

Wenn Sie ein Pseudocode oder ein Codebeispiel bereitstellen könnten, würde es wirklich helfen.

Vielen Dank im Voraus :)

Nach Feedback von @grafito, das ist die Lösung, die ich mit aufkommen. Scheint vollständig zu funktionieren. Wenn alle Benutzer verbunden sind, bleiben wir in einer Schleife stecken, bis eine verfügbar ist.

namespace UnitTestProject1 
{ 
[TestFixture] 
[Parallelizable] 
[Category("ParallelTest2")] 
public class ParallelTestingExampleV2_A : BaseTest 
{ 
    [Test] 
    public void TestMethod1() 
    { 
     Trace.WriteLine($"Test1 has user {AvailableUser.UserName}"); 
     Thread.Sleep(50000); 
     Trace.WriteLine($"Test1 sleeping for 50000ms so one user is Connected."); 
    } 
} 
[TestFixture] 
[Parallelizable] 
[Category("ParallelTest2")] 
public class ParallelTestingExampleV2_B : BaseTest 
{ 
    [Test] 
    public void TestMethod2() 
    { 
     Trace.WriteLine($"Test2 has user {AvailableUser.UserName}"); 
    } 
} 
[TestFixture] 
[Parallelizable] 
[Category("ParallelTest2")] 
public class ParallelTestingExampleV2_C : BaseTest 
{ 
    [Test] 
    public void TestMethod3() 
    { 
     Trace.WriteLine($"Test3 has user {AvailableUser.UserName}"); 
    } 
} 

[SetUpFixture] 
public class TestFixtureForTestsNamespace 
{ 
    public static ListOfUsers ListOfAllPossibleUsers; 

    [OneTimeSetUp] 
    public void RunBeforeAllTestsAreExecutedInSomeNamespace() 
    { 
     GetPoolOfUsers(); 
    } 

    private static void GetPoolOfUsers() 
    { 
     var oneUser = new User 
     { 
      UserName = "a", 
      Password = "a" 
     }; 

     var secondUser = new User 
     { 
      UserName = "b", 
      Password = "b" 
     }; 
     ListOfAllPossibleUsers = new ListOfUsers() { oneUser, secondUser }; 
    } 
} 

public class BaseTest 
{ 
    protected User AvailableUser; 

    [SetUp] 
    public void SetupForEveryTestMethod() 
    { 
     AvailableUser = TestFixtureForTestsNamespace.ListOfAllPossibleUsers.GetAvailableUser(); 
    } 

    [TearDown] 
    public void TearDownForEveryTestMethod() 
    { 
     TestFixtureForTestsNamespace.ListOfAllPossibleUsers.ReleaseUser(AvailableUser); 
    } 
} 



public class User 
{ 
    internal string UserName = ""; 
    internal string Password = ""; 
    internal bool Connected; 
} 

public class ListOfUsers : List<User> 
{ 
    internal void ReleaseUser(User userToBeReleased) 
    { 
     lock (this) { userToBeReleased.Connected = false; } 
    } 

    internal User GetAvailableUser() 
    { 
     User user = null; 
     while (user == null) 
     { 
      lock (this) 
      { 
       for (int i = 0; i < Count && user == null; i++) 
       { 
        if (!this[i].Connected) 
         user = this[i]; 
       } 
       if (user != null) 
        user.Connected = true; 
      } 
      Thread.Sleep(200); 
     } 
     return user; 
    } 
} 

}

+0

Sie Unit-Tests markiert haben, scheinen aber Integrationstests zu beschreiben. Die Strategien für die beiden sind nicht unbedingt gleich. – Kritner

Antwort

1

erstellen einen Pool von Benutzern und, wenn einen "neuen" Test ausgeführt wird, einen Benutzer aus dem Benutzer-Pool erhalten. Wenn alle Benutzer bereits verbunden sind, warten Sie auf einen Benutzer (freigegeben am Ende eines abgeschlossenen Tests).

+0

Danke, ich habe Ihren Code verwendet, um meine Implementierung abzuschließen, über die ich in meine Frage hinzugefügt habe. –

0

Machen Sie die Tests unabhängig - erstellen Sie einen neuen Benutzer für jeden Test!

Der "Pool" des Benutzers kommt mit Komplexität und kann Probleme für Sie einführen. Wenn es mühsam ist, für jeden Test einen neuen Benutzer zu erstellen, müssen Sie dieses Problem lösen. Aber es wird es wert sein!

+0

Ich liebe diese Option und ich denke, dass dies eine ideale Lösung wäre. Es ist jedoch die mit Abstand schwerste Lösung. Die Architektur der Anwendung lässt dies nicht ohne weiteres zu. Die Benutzer sind eng mit den Daten verbunden. Nur bestimmte Benutzer können auf bestimmte Inhalte zugreifen. Es gibt keine API für mich, um Testbenutzer einfach zu erstellen und zu löschen. Dies würde große Anstrengungen seitens des Entwicklungsteams erfordern, um dies zu ermöglichen. Obwohl das mein Traum ist, haha, wird es mir kurzfristig nicht helfen –

0

IMO gibt es keine Silberkugel Lösung - für jede Anwendung eine andere Geschichte. Ich kann nur nur ein paar allgemeine Zwecke Regeln schaffen, die

  • trennen Sie die Benutzer helfen kann - in der Tat wie meine Kollegen bereits gesagt haben, sollten die gleichen Tests nicht mit dem gleichen Benutzer laufen

  • Separate durch Abstraktionen, die von Ihrer Anwendung unterstützt werden. Wenn Ihre Anwendung beispielsweise die Mandantenfähigkeit unterstützt, sollten Sie versuchen, parallele Tests in verschiedenen Mandanten auszuführen. Andernfalls können die Tests beim Zugriff auf speicherinterne Datenstrukturen, die während des Testlaufs geändert wurden, zusammenstoßen.

  • Stellen Sie sicher, dass das Erstellen eines neuen Benutzers, neuen Mandanten usw. für jeden Test keinen signifikanten Overhead verursacht. weil sonst Ihre Tests einfach zu langsam sind

  • Trennen Sie die Persistenzschichten. Wenn Ihre Anwendung beispielsweise mit einer bestimmten Datenbank arbeitet, können zwei parallel ausgeführte Tests dieselben Zeilen und Spalten in derselben Tabelle ändern, sodass sie erneut zusammenstoßen.

  • Tests sollten nicht die Anwendung Persistence-Schichten/In-Memory-Datenstrukturen in einem "schmutzigen" Zustand verlassen. Dies gilt auch für aufeinanderfolgende Tests, nicht nur parallel. Leider ist das nicht immer möglich.

  • Wenn das letzte Element wirklich unmöglich zu erreichen ist, verwenden Sie das Pooling nur, wenn es keine andere Möglichkeit gibt.Wenn Sie beispielsweise einen Pool von Benutzern erstellen, wird zu einem bestimmten Zeitpunkt unter demselben Benutzer A & B ausgeführt. Wenn Test A die dem Benutzer zugewiesenen Datenstrukturen unbrauchbar gemacht (verschmutzt gelassen) hat, dann wird Test B wahrscheinlich nicht wie erwartet ausgeführt.

  • Vermeiden Sie unnötige Tests. Ein Entwickler verfügt über Komponententests, Integrationstests, Funktionstests - alle sind gültige Tools, um die Software zu überprüfen. Also wenn etwas durch Unit-Test abgedeckt werden kann - gehen Sie dafür, sonst gehen Sie mit Integrationstests, ansonsten mit vollwertigen Funktionstests. Es scheint, dass Sie einen Funktionstest beschreiben (wie sich immer beim System anmelden usw.). Ist es wirklich nötig? Ist es nicht ein Overhead? Vielleicht sollten Sie versuchen, das Verhalten der vorhandenen Komponente in einem Integrationstest zu überprüfen, der nur einen Teil Ihrer Anwendung ausführen wird/nur eine relevante Komponente ... Ich weiß, dass dieser Rat zu allgemein ist und nicht nur im Zusammenhang mit Ihrer Frage gilt , aber es kann bei der Gestaltung der richtige Test-Infrastruktur für die gesamte Anwendung

hoffte, das hilft

0

nach Feedback von @grafito implizit helfen, das ist die Lösung, die ich mit aufkommen. Scheint vollständig zu funktionieren. Wenn alle Benutzer verbunden sind, bleiben wir in einer Schleife stecken, bis eine verfügbar ist.

namespace UnitTestProject1 
{ 
[TestFixture] 
[Parallelizable] 
[Category("ParallelTest2")] 
public class ParallelTestingExampleV2_A : BaseTest 
{ 
    [Test] 
    public void TestMethod1() 
    { 
     Trace.WriteLine($"Test1 has user {AvailableUser.UserName}"); 
     Thread.Sleep(50000); 
     Trace.WriteLine($"Test1 sleeping for 50000ms so one user is Connected."); 
    } 
} 
[TestFixture] 
[Parallelizable] 
[Category("ParallelTest2")] 
public class ParallelTestingExampleV2_B : BaseTest 
{ 
    [Test] 
    public void TestMethod2() 
    { 
     Trace.WriteLine($"Test2 has user {AvailableUser.UserName}"); 
    } 
} 
[TestFixture] 
[Parallelizable] 
[Category("ParallelTest2")] 
public class ParallelTestingExampleV2_C : BaseTest 
{ 
    [Test] 
    public void TestMethod3() 
    { 
     Trace.WriteLine($"Test3 has user {AvailableUser.UserName}"); 
    } 
} 

[SetUpFixture] 
public class TestFixtureForTestsNamespace 
{ 
    public static ListOfUsers ListOfAllPossibleUsers; 

    [OneTimeSetUp] 
    public void RunBeforeAllTestsAreExecutedInSomeNamespace() 
    { 
     GetPoolOfUsers(); 
    } 

    private static void GetPoolOfUsers() 
    { 
     var oneUser = new User 
     { 
      UserName = "a", 
      Password = "a" 
     }; 

     var secondUser = new User 
     { 
      UserName = "b", 
      Password = "b" 
     }; 
     ListOfAllPossibleUsers = new ListOfUsers() { oneUser, secondUser }; 
    } 
} 

public class BaseTest 
{ 
    protected User AvailableUser; 

    [SetUp] 
    public void SetupForEveryTestMethod() 
    { 
     AvailableUser = TestFixtureForTestsNamespace.ListOfAllPossibleUsers.GetAvailableUser(); 
    } 

    [TearDown] 
    public void TearDownForEveryTestMethod() 
    { 
     TestFixtureForTestsNamespace.ListOfAllPossibleUsers.ReleaseUser(AvailableUser); 
    } 
} 



public class User 
{ 
    internal string UserName = ""; 
    internal string Password = ""; 
    internal bool Connected; 
} 

public class ListOfUsers : List<User> 
{ 
    internal void ReleaseUser(User userToBeReleased) 
    { 
     lock (this) { userToBeReleased.Connected = false; } 
    } 

    internal User GetAvailableUser() 
    { 
     User user = null; 
     while (user == null) 
     { 
      lock (this) 
      { 
       for (int i = 0; i < Count && user == null; i++) 
       { 
        if (!this[i].Connected) 
         user = this[i]; 
       } 
       if (user != null) 
        user.Connected = true; 
      } 
      Thread.Sleep(200); 
     } 
     return user; 
    } 
} 

}

Verwandte Themen