我正在努力改进我的自动功能测试以并行运行以减少测试套件的运行时间。我遇到的问题是了解如何在测试并行运行时管理我的测试数据。
更具体地说,当测试连续运行时,我使用单个用户登录应用程序而没有问题。但是,当尝试使用该用户运行多个测试并尝试登录时,这不起作用。我有其他用户可以用来登录。我的问题是我如何管理这些用户,以便1个测试使用1个唯一用户?在他获得自由之前,没有其他测试可以触及该用户。
如果您可以提供一些伪代码或代码示例,那将非常有帮助。
提前致谢:)
在@grafito的反馈之后,这是我提出的解决方案。似乎完全有效。当所有用户都已连接时,我们会陷入一个循环,直到有一个用户可用。
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 :(得分:1)
创建用户池,并在运行“新”测试时从用户池中获取用户。如果所有用户已连接,请等待用户(在终止测试结束时释放)。
internal class UserDef
{
internal string Login = "" ; // eventually add password
internal bool Connected = false ;
}
internal class UserDefs : List<UserDef>
{
internal ReleaseUser(UserDef userdef)
{
lock(this) { userdef.Connected=false ; }
}
internal UserDef GetAvailableUser()
{
UserDef result=null ;
while (result==null)
{
lock(this)
{
for (int i=0;i<Count && result==null;i++) if (!this[i].Connected) result=this[i] ;
if (result!=null) result.Connected = true ;
}
system.Threading.Thread.Sleep(200) ;
}
return result ;
}
}
答案 1 :(得分:0)
使测试独立 - 为每个测试创建一个新用户!
&#34; pool&#34;用户的复杂性,可以为您带来问题。如果为每个测试创建新用户很麻烦,则需要解决该问题。但它值得!
答案 2 :(得分:0)
IMO这里没有银弹解决方案 - 每个应用程序都有不同的故事。我只能提供一些可以帮助的通用规则
将用户分开 - 事实上我的同事已经说过,同样的测试不应该与同一个用户一起运行
通过您的应用程序支持的抽象分开。例如,如果您的应用程序支持多租户,请尽力在不同的租户中运行并行测试。否则,在访问内存数据结构时,测试可能会发生冲突,在测试运行期间会发生变化
话虽如此,确保为每次测试创建新用户,新租户等不会带来显着的开销,因为否则您的测试会太慢
分隔持久层。例如,如果您的应用程序使用某种数据库,则并行运行的2个测试可以在同一个表中更改相同的行和列,这样它们又会发生冲突。
测试不应该将应用程序持久层/内存中的数据结构保留在“脏”状态。州。即使是连续测试,这也是正确的,不仅是并行测试。不幸的是,它并非总是可行。
如果最后一项确实无法实现,请仅在没有其他选择时使用池。例如,如果您创建一个用户池,在某个时间点测试A&amp; B将在同一用户下运行。如果测试A使分配给用户的数据结构不可用(将它们弄脏),那么测试B可能无法按预期运行
避免不必要的测试。开发人员具有单元测试,集成测试和功能测试 - 所有这些都是检查软件的有效工具。因此,如果可以通过单元测试来覆盖某些内容 - 那就去做吧,否则就进行集成测试,否则就进行全面的功能测试。您似乎在描述功能测试(如始终登录系统等)。真的需要吗?这不是开销吗?也许您应该尝试检查集成测试中现有组件的行为,该测试只运行应用程序的一部分/仅运行相关组件...我知道此建议过于笼统,不仅适用于您的问题的上下文中,但它可以隐含地帮助为整个应用程序设计正确的测试基础架构
希望这有帮助
答案 3 :(得分:0)
在@grafito的反馈之后,这是我提出的解决方案。似乎完全有效。当所有用户都已连接时,我们会陷入一个循环,直到有一个用户可用。
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;
}
}
}