gpt4 book ai didi

c# - 当知道 xUnit 并行运行时,使用什么策略进行集成测试?

转载 作者:行者123 更新时间:2023-12-05 05:11:41 24 4
gpt4 key购买 nike

我将 dotnet core 与 xUnit 一起用于单元测试和集成测试。我以这种方式遵循 Given-Then-When 哲学为我的所有测试创建了一个基本抽象类:

namespace ToolBelt.TestSupport
{
public abstract class Given_WhenAsync_Then_Test
: IDisposable
{
protected Given_WhenAsync_Then_Test()
{
Task.Run(async () => { await SetupAsync();}).GetAwaiter().GetResult();
}

private async Task SetupAsync()
{
Given();
await WhenAsync();
}

protected abstract void Given();

protected abstract Task WhenAsync();

public void Dispose()
{
Cleanup();
}

protected virtual void Cleanup()
{
}
}
}

作为总结,对于每个事实(then),构造函数(given)和 Action (when)被再次执行。这对于实现幂等测试非常有趣,因为每个事实都应该可以独立运行(给定的应该是幂等的)。这非常适合单元测试。

但是对于集成测试,有时我会在这样的场景中发现问题:

我有一个要测试的 mongoDb 存储库实现。我有测试来验证我可以在上面写,还有其他测试来验证我可以从中读取。但是由于所有这些测试都是并行运行的,所以我必须非常注意如何设置 Given 以及如何以及何时清理上下文。

测试类A:

  1. 给定:我向数据库写入文档
  2. 时间:我阅读文档
  3. 那么:结果就是预期的文档

测试类 B:

  1. 给定: repo 可用
  2. 时间:我写文档
  3. 然后:它毫无异常(exception)地写入

现在假设两个测试类并行运行,有时会出现以下问题:

  • 测试 A 执行并写入一个 Id 为 1 的文档。同时,测试 B 尝试在其 when 中写入一个 Id 为 1 的文档,但它失败了,因为同一个文档中已经有一个文档具有相同 ID 的数据库。
  • 测试 B 执行,它有一个拆卸/清理,在测试结束时删除文档。与此同时,测试 A 正要读取一份预期存在的文档...但失败了,因为该文档已被删除(从测试 B 中删除)

问题是:是否有可能并行运行集成测试并实现幂等 Given 而不会因为一个测试混淆了另一个测试的数据而遇到问题?

我想到了一些想法,但我没有这方面的经验,所以我正在寻找意见和解决方案。

  • 解决方案 A:确保每个测试类都使用其他测试无法访问的数据。例如,通过为测试数据提供不同的 ID。这确实可以解决问题,但它迫使开发人员了解其他测试正在使用哪些 ID。
  • 解决方案 B:使用某种组装 Given 和 Teardown 为每个测试准备场景。同样,我们会依赖比测试类本身更强大的东西,它似乎违反了我想要遵循的 GivenThenWhen 理念。

xUnit 有可能在测试之间有不同的共享上下文,但我也看不出它如何适合我的模板:https://xunit.github.io/docs/shared-context .

您如何使用 xUnit 处理这些集成测试场景?塔


更新 1:这是我如何使用 GTW 理念和 xUnit 创建测试的示例。这些事实有时会失败,因为它们无法插入具有已存在 Id 的文档(因为使用具有相同 id 的文档的其他测试类正在同时运行并且尚未清理)

public static class GetAllTests
{
public class Given_A_Valid_Filter_When_Getting_All
: Given_WhenAsync_Then_Test
{
private ReadRepository<FakeDocument> _sut;
private Exception _exception;
private Expression<Func<FakeDocument, bool>> _filter;
private IEnumerable<FakeDocument> _result;
private IEnumerable<FakeDocument> _expectedDocuments;

protected override void Given()
{
_filter = x => true;
var cursorServiceMock = new Mock<ICursorService<FakeDocument>>();
var all = Enumerable.Empty<FakeDocument>().ToList();
cursorServiceMock
.Setup(x => x.GetList(It.IsAny<IAsyncCursor<FakeDocument>>()))
.ReturnsAsync(all);
var cursorService = cursorServiceMock.Object;

var documentsMock = new Mock<IMongoCollection<FakeDocument>>();
documentsMock
.Setup(x => x.FindAsync(It.IsAny<Expression<Func<FakeDocument, bool>>>(),
It.IsAny<FindOptions<FakeDocument, FakeDocument>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(default(IAsyncCursor<FakeDocument>));
var documents = documentsMock.Object;

_sut = new ReadRepository<FakeDocument>(documents, cursorService);
_expectedDocuments = all;
}

protected override async Task WhenAsync()
{
try
{
_result = await _sut.GetAll(_filter);
}
catch (Exception exception)
{
_exception = exception;
}
}

[Fact]
public void Then_It_Should_Execute_Without_Exceptions()
{
_exception.Should().BeNull();
}

[Fact]
public void Then_It_Should_Return_The_Expected_Documents()
{
_result.Should().AllBeEquivalentTo(_expectedDocuments);
}
}

public class Given_A_Null_Filter_When_Getting_All
: Given_WhenAsync_Then_Test
{
private ReadRepository<FakeDocument> _sut;
private ArgumentNullException _exception;
private Expression<Func<FakeDocument, bool>> _filter;

protected override void Given()
{
_filter = default;
var cursorService = Mock.Of<ICursorService<FakeDocument>>();
var documents = Mock.Of<IMongoCollection<FakeDocument>>();
_sut = new ReadRepository<FakeDocument>(documents, cursorService);
}

protected override async Task WhenAsync()
{
try
{
await _sut.GetAll(_filter);
}
catch (ArgumentNullException exception)
{
_exception = exception;
}
}

[Fact]
public void Then_It_Should_Throw_A_ArgumentNullException()
{
_exception.Should().NotBeNull();
}
}
}

更新 2: 如果我制作随机 ID,有时我也会遇到问题,因为从数据库中检索的预期文档包含的项目多于测试预期的项目(因为,同样,其他并行运行的测试在数据库中写入了更多文档)。

最佳答案

我不久前发现 xUnit 内置了对异步场景的支持,其中包含 IAsyncLifetime

现在,根据 my open source library

我有以下 GivenWhenThen 模板

using System.Threading.Tasks;
using Xunit;

namespace Sasw.TestSupport.XUnit
{
public abstract class Given_When_Then_Test_Async
: IAsyncLifetime
{
public async Task InitializeAsync()
{
await Given();
await When();
}

public async Task DisposeAsync()
{
await Cleanup();
}

protected virtual Task Cleanup()
{
return Task.CompletedTask;
}

protected abstract Task Given();

protected abstract Task When();
}
}

按照并行执行方法,我通常在测试的前提条件下创建唯一的数据库、流等,这样我就根本没有任何逻辑共享资源。每个测试使用相同的服务器、不同的流、数据库或集合。

如果出于某种原因,我需要禁用并行 xUnit 测试执行,我做的第一件事就是重新考虑我的设计方法,也许这是一种气味。如果仍然有一些测试在并行运行时失败,因为有一些我无法控制的共享资源,我通过在我的测试项目上添加一个文件夹 Properties 来禁用并行 xUnit 行为文件 AssemblyInfo,我在其中指示 xUnit 在该项目/程序集上,测试应按顺序运行。

using Xunit;
#if DEBUG
[assembly: CollectionBehavior(DisableTestParallelization = false)]
#else
// Maybe in DEBUG I want to run in parallel and
// in RELEASE mode I want to run sequentially, so I can have this.
[assembly: CollectionBehavior(DisableTestParallelization = true)]
#endif

有关在我的一个视频教程中使用上述方法的一些测试方法的更多信息(抱歉,是西​​类牙语):https://www.youtube.com/watch?v=dyVEayGwU3I

关于c# - 当知道 xUnit 并行运行时,使用什么策略进行集成测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55297811/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com