运行 xUnit 使用异步方法在 Teamcity 上进行测试

Running xUnit tests on Teamcity using async methods

我进行了以下 xUnit 测试,它使用 HttpClient 调用网络服务器上的状态 api 方法。

[Fact]
public void AmIAliveTest()
{
    var server = TestServer.Create<Startup>();

    var httpClient = server.HttpClient;
    var response = httpClient.GetAsync("/api/status").Result;

    response.StatusCode.Should().Be(HttpStatusCode.OK);
    var resultString = response.Content.ReadAsAsync<string>().Result;

    resultString.Should().Be("I am alive!");
}

这个测试在当地 运行 是没问题的。但是当我提交代码并尝试 运行 在 TeamCity 构建服务器上进行相同的测试时,它永远 运行 了。我什至必须终止 xunit 运行ner 进程,因为停止构建不会停止此进程。

然而当我这样写测试时

[Fact]
public async void AmIAliveTest()
{
   var server = TestServer.Create<Startup>();

   var httpClient = server.HttpClient;
   var response = await httpClient.GetAsync("/api/status");

   response.StatusCode.Should().Be(HttpStatusCode.OK);
   var resultString = await response.Content.ReadAsAsync<string>();

   resultString.Should().Be("I am alive!");
}

它 运行 在本地和 TeamCity 上都很好。

我现在担心的是我忘记像第二个变体那样编写测试,而且有时 teamcity 构建会挂起。

任何人都可以向我解释为什么 teamcity buildserver 上的 xUnit 运行ning 没有首先正确地 运行ning 测试吗?有解决这个问题的方法吗?

你的测试失败了。

xUnit 需要 Task return 才能确定测试失败,更重要的是,它是异常返回到 xUnit 的句柄。

通过 public async void,您有一个例外的孤立 Task。由此产生的异常当然没有得到处理,因此当然会破坏整个过程。因此你的测试 运行 停止了。

您可以通过像这样编写所有测试来修复它...

[Fact]
public async Task AmIAliveTest()
{
   var server = TestServer.Create<Startup>();

   var httpClient = server.HttpClient;
   var response = await httpClient.GetAsync("/api/status");

   response.StatusCode.Should().Be(HttpStatusCode.OK);
   var resultString = await response.Content.ReadAsAsync<string>();

   resultString.Should().Be("I am alive!");
}

Can anybody explain to me why xUnit running on the teamcity buildserver is not running the test correctly in the first place?

首先,我会检查您的 xUnit 版本 - 您应该 运行 最近发布的 2.0。我怀疑您的本地版本可能已过时。

核心问题在这一行:

var resultString = response.Content.ReadAsAsync<string>().Result;

我怀疑您 运行 喜欢我在博客上描述的 deadlock situationHttpClient在某些平台上有一些方法没有正确使用ConfigureAwait(false),因此会出现这种死锁。 xUnit 2.0 在其所有单元测试中安装了单线程 SynchronizationContext,这提供了死锁场景的另一半。

正确的解决方案是将 Result 替换为 await,并将单元测试方法的 return 类型从 void 更改为 Task .