我有一个.NET 6应用程序(带有ASP.NET核心web部件).我使用cypress进行E2E测试.在我们迁移到之后.净额6自.NET框架,我想在web应用程序上运行E2E cypress测试.
我的过程是在C中实际运行nUnit测试,初始化内存中的SQLite数据库,然后将此数据库连接用于我的应用程序.我使用这种方法在实际测试运行之前创建E2E测试所需的数据.然后我从C#单元测试运行cypress.这一切都很顺利.NET Framework,web应用程序实际上是在测试期间运行的,因此它在IIS中的真实URL上可用.
具有NET 6,我们有WebApplicationFactory
class,允许在内存中运行应用程序.然而,我希望我的web应用程序可以在一个带有真实端口的真实URL下使用,这样我就可以针对它启动cypress测试.
在探索了this GitHub discussion多个 idea 之后,我创建了一个真正的测试应用程序工厂(具有真正的IP和端口):
public abstract class RealTestServerWebAppFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
private bool _disposed;
private IHost _host;
public RealTestServerWebAppFactory()
{
ClientOptions.AllowAutoRedirect = false;
ClientOptions.BaseAddress = new Uri("http://localhost");
}
public string ServerAddress
{
get
{
EnsureServer();
return ClientOptions.BaseAddress.ToString();
}
}
public override IServiceProvider Services
{
get
{
EnsureServer();
return _host!.Services!;
}
}
// Code created with advices from https://github.com/dotnet/aspnetcore/issues/4892
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
// Final port will be dynamically assigned
builder.UseUrls("http://127.0.0.1:0");
builder.ConfigureServices(ConfigureServices);
}
protected override IHost CreateHost(IHostBuilder builder)
{
// Create the host for TestServer now before we
// modify the builder to use Kestrel instead.
var testHost = builder.Build();
// Modify the host builder to use Kestrel instead
// of TestServer so we can listen on a real address.
builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel());
// Create and start the Kestrel server before the test server,
// otherwise due to the way the deferred host builder works
// for minimal hosting, the server will not get "initialized
// enough" for the address it is listening on to be available.
// See https://github.com/dotnet/aspnetcore/issues/33846.
_host = builder.Build();
_host.Start();
// Extract the selected dynamic port out of the Kestrel server
// and assign it onto the client options for convenience so it
// "just works" as otherwise it'll be the default http://localhost
// URL, which won't route to the Kestrel-hosted HTTP server.
var server = _host.Services.GetRequiredService<IServer>();
var addresses = server.Features.Get<IServerAddressesFeature>();
ClientOptions.BaseAddress = addresses!.Addresses
.Select(x => new Uri(x))
.Last();
// Return the host that uses TestServer, rather than the real one.
// Otherwise the internals will complain about the host's server
// not being an instance of the concrete type TestServer.
// See https://github.com/dotnet/aspnetcore/pull/34702.
testHost.Start();
return testHost;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!_disposed)
{
if (disposing)
{
_host?.Dispose();
}
_disposed = true;
}
}
protected abstract void ConfigureServices(IServiceCollection services);
/// <summary>
/// Makes sure that the server has actually been run.
/// It must be executed at lest once to have the address and port assigned and the app fully working
/// </summary>
private void EnsureServer()
{
if (_host is null)
{
using var _ = CreateDefaultClient();
}
}
}
然后我创建了一个从RealTestServerWebAppFactory
派生的类,在这里我设置了一些测试依赖注入,但这对我们的示例并不重要.让我们把这个子元素叫做MyServerTestApp
班.
测试中的实例创建如下所示:
var serverTestApp = new MyServerTestApp();
serverTestApp.ServerAddress;
在第二行,调用EnsureServer()
方法,为应用程序创建默认客户端.它还调用RealTestServerWebAppFactory
类中的CreateHost(IHostBuilder builder)
来完成所有的魔法.在本地,在这一行之后,我有了应用程序的URL,我可以通过web浏览器(或E2E测试)访问它.
现在,问题是测试在本地运行良好.然而,更准确地说,我也在CI-TeamCity上执行它们.TeamCity用于运行这些测试的dotnet test
命令如下所示:
"C:\Program Files\dotnet\dotnet.exe" test C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\E2ETests\bin\Release\net6.0-windows\MyApp.E2ETests.dll /logger:logger://teamcity /TestAdapterPath:C:\TeamCity\buildAgent\plugins\dotnet\tools\vstest15 /logger:console;verbosity=normal
实际上,dotnet test
命令的使用非常简单.但是,TeamCity未能执行这些测试,以下情况除外:
[12:47:54][dotnet test] A total of 1 test files matched the specified pattern.
[12:47:56][dotnet test] NUnit Adapter 4.2.0.0: Test execution started
[12:47:56][dotnet test] Running all tests in C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\E2ETests\bin\Release\net6.0-windows\MyApp.E2ETests.dll
[12:47:58][dotnet test] NUnit3TestExecutor discovered 10 of 10 NUnit test cases using Current Discovery mode, Non-Explicit run
[12:48:07][dotnet test] Setup failed for test fixture MyApp.E2ETests.SomeTestsClass
[12:48:07][dotnet test] System.InvalidOperationException : Sequence contains no elements
[12:48:07][dotnet test] StackTrace: at System.Linq.ThrowHelper.ThrowNoElementsException()
[12:48:07][dotnet test] at System.Linq.Enumerable.Last[TSource](IEnumerable`1 source)
[12:48:07][dotnet test] at MyApp.E2ETests.Infrastructure.RealTestServerWebAppFactory`1.CreateHost(IHostBuilder builder) in C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\MyApp.E2ETests\Infrastructure\RealTestServerWebAppFactory.cs:line 85
[12:48:07][dotnet test] at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
[12:48:07][dotnet test] at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
[12:48:07][dotnet test] at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
[12:48:07][dotnet test] at MyApp.E2ETests.Infrastructure.RealTestServerWebAppFactory`1.EnsureServer() in C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\MyApp.E2ETests\Infrastructure\RealTestServerWebAppFactory.cs:line 122
[12:48:07][dotnet test] at MyApp.E2ETests.Infrastructure.RealTestServerWebAppFactory`1.get_ServerAddress() in C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\MyApp.E2ETests\Infrastructure\RealTestServerWebAppFactory.cs:line 35
System.InvalidOperationException : Sequence contains no elements
似乎该代码:
var addresses = server.Features.Get<IServerAddressesFeature>();
返回TeamCity正在运行的地址的空连接.因此,该代码:
ClientOptions.BaseAddress = addresses!.Addresses
.Select(x => new Uri(x))
.Last();
System.InvalidOperationException : Sequence contains no elements
失败.
奇怪的是,如果我使用与TeamCity手动使用cmd
相同的dotnet test
命令来运行这些测试,它就会正常工作.它在同一台CI机器上工作,但从cmd
开始手动执行,而不是TeamCity的构建步骤.CI计算机运行Windows Server 2019.
我一直在想,这可能与用于运行TeamCity的用户的权限有关.然而,TeamCity的Windows服务使用"本地系统"帐户,因此据我所知,它应该拥有所有权限.
感谢您的任何帮助/ idea :)