sam*_*ba2 6 c# integration-testing xunit .net-core
TLDR:为了集成测试 .NET Core Web 应用程序,我们有WebApplicationFactory. 我如何为工人服务做同样的事情?
鉴于此工人服务:
// Program.cs
using SampleWorker;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services => { services.AddHostedService<Worker>(); })
.Build();
host.Run();
Run Code Online (Sandbox Code Playgroud)
// Worker.cs
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// - connect to a middleware (data source)
// - connect to database (data sink)
// - received events from middleware, transform + persist in database
}
}
Run Code Online (Sandbox Code Playgroud)
我希望能够编写一个端到端测试,通过测试容器(有效)和被测单元(工作服务)提供数据源和接收器,以某种方式直接通过 .NET Core。对于 Web 应用程序,您将使用WebApplicationFactory<Program>. 我正在寻找一种方法来对 Worker 服务执行相同的操作。
这是我希望能够编写的测试:
public class E2eTest
{
/// <summary>
/// Tests the full round trip from an event being
/// - picked up by the worker service
/// - processed and transformed
/// - persisted to the database.
/// </summary>
[Fact]
public void EventIsProcessedAndWrittenToDatabase()
{
// arrange
// - start middleware as data source (testcontainers)
// - start database as data sink (testcontainers)
// TODO there is no WorkerFactory.
// How can I start my SampleWorker with all the config in Program.cs ?
var unitUnderTest = new WorkerFactory<Program>();
// act
// - publish an event to the middleware (data source)
// assert
// - check that there are entries in the database (data sink)
}
}
Run Code Online (Sandbox Code Playgroud)
通过将 SamplerWorker 也作为 Docker 容器运行,我已经获得了该测试的类似版本。这利用了 Testcontainer 的ImageFromDockerfileBuilder。然而,我希望能够轻松地调试我的被测单元,这对于我当前的 Docker 方法来说变得非常复杂。
有任何想法吗?谢谢
TL;DR:这是我当前的实现要点。
大致基于微软的WebApplicationFactory,我想出了这个。我确信有些场景它无法处理,并且还有很大的改进空间,但它满足了我的需求:一个进程内运行的辅助服务,就像WebApplicationFactory. 这是在使用HostApplicationBuilder, 而您正在使用IHostBuilder. IHostBuilder但是,如果您想继续使用该代码,它很容易适应。上面链接的要点确实可以IHostBuilder正确处理。
Microsoft 的测试框架在程序集入口点上查找名为BuildWebHost、CreateWebHostBuilder、 和的方法CreateHostBuilder。框架将调用这些方法来获取创建进程内主机所需的内容。但是,在辅助服务中,我们的入口点是Program.cs,唯一声明的方法是<Main>$。所以我们需要给自己一个方法来检索构建器。我们还将声明Program为public; 这在以后很重要。
我们的Program.cs成为
var builder = CreateHostBuilder(new HostApplicationBuilderSettings { Args = args });
// If there's any builder setup you don't want in the tests, such as auth, do it here.
var host = builder.Build();
host.Run();
public sealed partial class Program
{
private static HostApplicationBuilder CreateHostBuilder(HostApplicationBuilderSettings settings)
{
var builder = Host.CreateApplicationBuilder(settings);
// The rest of your setup. builder.Services.AddStuff(), etc.
// Any builder setup you want to make it into the tests
// MUST be done in here.
...
}
}
Run Code Online (Sandbox Code Playgroud)
现在,我们需要一个工厂,它将采用通用参数,创建我们的构建器,并启动主机。WorkerServiceFactory.cs我为此创建了一个文件。我们还需要第二类来延迟我们的主机启动,以便IAsyncLifetime我们的工厂能够正常工作。我将其命名为“TestWorkerService.cs我们将在此处逐步进行”,下面提供了完整的代码。
注意:您可以执行更高级的工厂方法查找,这可以处理更多场景。我上面链接的要点正是这样做的。我选择在这里保持简单。
TestWorkerService.cs我们这里所有的工作都可以在构造函数中完成。我们收到一个用于创建主机的构建器参数,以及一个用于配置构建器的参数。然后,我们构建主机并启动它,确保不要阻塞等待主机完成的线程。
public sealed class TestWorkerService : IDisposable
{
internal readonly IHost Host;
internal TestWorkerService(
HostApplicationBuilder builder,
Action<HostApplicationBuilder> configure
)
{
configure(builder);
Host = builder.Build();
Host.StartAsync().GetAwaiter().GetResult();
}
public void Dispose()
{
Host.Dispose();
}
}
Run Code Online (Sandbox Code Playgroud)
WorkerServiceFactory.cs首先,我们需要发现应用程序的入口点。
var entryPoint = typeof(T).Assembly.EntryPoint!.DeclaringType!;
Run Code Online (Sandbox Code Playgroud)
然后,我们将在Program.cs. 我使用一个属性(下面提供)作为方法名称,以便您可以根据需要更改它。您只需要更改 中的方法名称Program.cs,然后覆盖该CreateBuilderMethodName属性。
var hostBuilderFactory = entryPoint.GetMethod(
CreateBuilderMethodName,
BindingFlags.Static | BindingFlags.NonPublic
)!;
Run Code Online (Sandbox Code Playgroud)
我们将构造要传递给构建器的设置,将环境名称设置为名为的变量Environment(在下面提供)。然后,我们使用这些设置调用工厂方法并将返回类型转换为HostApplicationBuilder.
var settings = new HostApplicationBuilderSettings
{
ApplicationName = entryPoint.Assembly.GetName().Name,
EnvironmentName = Environment
};
_builder = (HostApplicationBuilder)hostBuilderFactory.Invoke(null, new object?[] { settings })!;
Run Code Online (Sandbox Code Playgroud)
现在,我们将创建一个字段和属性来保存我们的TestWorkerService. 如果_workerService我们TestWorkerService使用. InitializeAsync() IAsyncLifetime例如,这允许在我们调用 时启动并访问 Docker 容器ConfigureHost。
protected virtual void ConfigureHost(HostApplicationBuilder builder) { }
private TestWorkerService? _workerService;
public TestWorkerService WorkerService =>
_workerService ??= new TestWorkerService(_builder, ConfigureHost);
Run Code Online (Sandbox Code Playgroud)
这是完整的代码:
protected virtual void ConfigureHost(HostApplicationBuilder builder) { }
private TestWorkerService? _workerService;
public TestWorkerService WorkerService =>
_workerService ??= new TestWorkerService(_builder, ConfigureHost);
Run Code Online (Sandbox Code Playgroud)
剩下要做的就是创建您想要在测试中使用的工厂。WorkerServiceFactory为此,我们将从 our 继承,并将 our (现在是公共的)Program作为程序集标记传递。
public abstract class WorkerServiceFactory<T> : IDisposable
{
private readonly HostApplicationBuilder _builder;
protected WorkerServiceFactory()
{
var entryPoint = typeof(T).Assembly.EntryPoint!.DeclaringType!;
var hostBuilderFactory = entryPoint.GetMethod(
CreateBuilderMethodName,
BindingFlags.Static | BindingFlags.NonPublic
)!;
var settings = new HostApplicationBuilderSettings
{
ApplicationName = entryPoint.Assembly.GetName().Name,
EnvironmentName = Environment
};
_builder = (HostApplicationBuilder)
hostBuilderFactory.Invoke(null, new object?[] { settings })!;
}
protected virtual void ConfigureHost(HostApplicationBuilder builder) { }
public void Dispose()
{
_workerService?.Dispose();
ServiceScope.Dispose();
GC.SuppressFinalize(this);
}
public void Start()
{
_ = WorkerService;
}
protected virtual string CreateBuilderMethodName { get; } = "CreateHostBuilder";
protected virtual string Environment { get; } = Environments.Development;
public IServiceProvider Services => WorkerService.Host.Services;
private TestWorkerService? _workerService;
public TestWorkerService WorkerService =>
_workerService ??= new TestWorkerService(_builder, ConfigureHost);
// Optional: creates a service scope for you.
/* public TService GetService<TService>()
where TService : class => ServiceScope.ServiceProvider.GetRequiredService<TService>();
private IServiceScope? _serviceScope;
private IServiceScope ServiceScope => _serviceScope ??= Services.CreateScope();*/
}
Run Code Online (Sandbox Code Playgroud)
您现在应该能够像使用 一样使用它WebApplicationFactory,减去一些您可以根据需要添加的更高级功能。
| 归档时间: |
|
| 查看次数: |
929 次 |
| 最近记录: |