我正在使用 NUnit 为 ASP NET Core 应用程序编写集成测试。我在测试中观察到一个奇怪的行为,似乎所有测试都在我的 SUT 中共享相同的静态属性。
我正在使用 WebApplicationFactory 来引导我的应用程序并与其交互。我的假设是,我在每个 WebApplicationFactory 中使用应用程序的“新鲜”实例,并且每个测试使用一个工厂......
我创建了一个最小的可重现示例,其中只有一个在启动时递增的静态 int 字段。端点返回字段的值。 我的测试重复了多次,预计总是返回 1。但正如您所料,它在第二次运行期间失败了。
程序.cs
internal class Program
{
private static int a = 0;
private static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
a++;
app.MapGet("/test", () => a);
app.Run();
}
}
单元测试
using Microsoft.AspNetCore.Mvc.Testing;
namespace Tests
{
public class Tests
{
[Test]
[Repeat(3)]
public async Task Test1()
{
var factory = new WebApplicationFactory<Program>();
var client = factory.CreateClient();
var res = await client.GetAsync("/test");
var number = int.Parse(await res.Content.ReadAsStringAsync());
Assert.That(number, Is.EqualTo(1));
}
}
}
我确信这与WebApplicationFactory如何启动程序有关,但这仍然是非常意外的,并且当我阅读docs时没有提及它。 也许这与我使用 NUnit 而不是文档中的 xUnit 有关?但是 NUnit 的行为是如何影响这一点的呢?
我还尝试使用 NUnits [NonParallelized] 来确保测试按顺序运行(无论如何这都是必需的),并且在我的非示例项目中,我每个测试使用一个类。
我发现了这种行为,因为我在启动时使用 BsonClassMap 注册 ClassMap,并且在测试中遇到了有关重复键的错误。
我主要是在寻找如何以某种方式运行我的测试的答案,即它们不共享状态。我也很好奇为什么会发生这种行为以及是否有意为之。
WebApplicationFactory
对于最小托管基本上为每个实例工厂实例(这是您的Assembly.EntryPoint
方法)运行
Main
,因此您将为工厂的每个创建和“启动”实例调用a++;
(它包括诸如 CreateClient
、Services
或 Server
之类的调用),并且由于 a
是一个静态字段,因此它将在整个应用程序生命周期中共享(基本上根据定义)。
至少有以下几种选择:
从使用静态转向单例服务。 IE。将
a
封装在某些服务中,并将其注册为单例并在适当的情况下进行解析。可以说,在一般情况下这是更好的方法。
对所有测试使用单个
WebApplicationFactory
实例(因此仅执行一次设置)。例如,将 OneTimeSetUp
与根夹具结合使用。例如,将如下内容添加到测试项目的根目录中:
public class GlobalFixture
{
public static WebApplicationFactory<Program> ApplicationFactory;
[OneTimeSetUp]
public void Setup() => ApplicationFactory = new WebApplicationFactory<Program>();
[TearDown]
public void TearDown() => ApplicationFactory.Dispose();
}
并在需要时使用
GlobalFixture.ApplicationFactory
。
我的假设是我正在为每个 WebApplicationFactory 使用应用程序的“新鲜”实例
它使用的是一个新的实例(我想说实际上是通过第二次运行时失败的测试证明的),但您仍然在整个过程中共享相同的静态字段。
也许这与我使用 NUnit 而不是文档中的 xUnit 有关?但是 NUnit 的行为是如何影响这一点的呢?
如您所见,它本身并不是 NUnit。