当全部运行时单元测试失败,但分别运行时通过。

4
我有大约12个单元测试来测试不同的场景,需要在这些测试中调用一个异步方法(有时一个测试中会多次调用)。当我运行“Run all”时,其中3个测试总是失败。但如果我使用“Run selected test”逐个运行它们,它们就会通过。我在输出中获得的异常信息如下:
System.AppDomainUnloadedException:尝试访问已卸载的AppDomain。如果测试启动了一个线程但没有停止它,则可能会发生这种情况。请确保测试启动的所有线程在完成之前都已停止。
由于代码相当庞大,我无法分享代码,也不知道从哪里开始,因此这里提供一个示例。
[TestMethod]
public async Task SampleTest()
{
    var someProvider = new SomeProvider();

    var result = await someProvider.IsSomethingValid();

    Assert.IsTrue(result == SomeProvider.Status.Valid);

    NetworkController.Disable();

    result = await someProvider.IsSomethingValid();

    Assert.IsTrue(result == SomeProvider.Status.Valid);

    NetworkController.Enable();
}

编辑: 另外两种失败的方法分别将时间设置为未来和过去。
[TestMethod]
public async Task SetTimeToFutureTest()
{
    var someProvider = new SomeProvider();

    var today = TimeProvider.UtcNow().Date;

    var result = await someProvider.IsSomethingValid();

    Assert.IsTrue(result == SomeProvider.Status.Valid);

    TimeProvider.SetDateTime(today.AddYears(1));

    var result2 = await someProvider.IsSomethingValid();

    Assert.IsTrue(result2 == SomeProvider.Status.Expired);
}

时间提供程序长这样:

public static class TimeProvider
{
    /// <summary> Normally this is a pass-through to DateTime.Now, but it can be overridden with SetDateTime( .. ) for testing or debugging.
    /// </summary>
    public static Func<DateTime> UtcNow = () => DateTime.UtcNow;

    /// <summary> Set time to return when SystemTime.UtcNow() is called.
    /// </summary>
    public static void SetDateTime(DateTime newDateTime)
    {
        UtcNow = () => newDateTime;
    }

    public static void ResetDateTime()
    {
        UtcNow = () => DateTime.UtcNow;
    }
}

编辑2:

    [TestCleanup]
    public void TestCleanup()
    {
        TimeProvider.ResetDateTime();
    }

其他方法类似,我将模拟时间/日期更改等操作。

我尝试通过从中获取.Result() 同步调用该方法等方式,但这并没有帮助。我在网上阅读了大量资料,但仍然苦苦挣扎。

有人遇到过同样的问题吗?任何提示将不胜感激。


你确定这与共享实例或静态类(例如NetworkController)无关吗? - bashis
@bashis,其中2个失败的方法没有调用NetworkController。 - user969153
@user969153 不行,测试是异步运行并访问共享的静态资源。它们会相互冲突。避免使用静态类,并创建一个可以被模拟和注入到依赖类中的服务,以便您的单元测试可以在隔离环境中进行测试。 - Nkosi
Nkosi,你是在指TimeProvider吗? - user969153
@user969153,时间提供者和网络控制器看起来都像静态类。虽然您可能正在创建 SUT 的新实例,但它依赖于在测试过程中被修改的外部类,这也暴露了 SUT 非常脆弱的事实。 - Nkosi
显示剩余7条评论
3个回答

1
我看不到你的测试初始化或清理工作,但可能由于所有测试方法都试图异步运行,测试运行器在执行清理前未等待所有任务完成。当你运行所有测试时,是否相同的几个方法失败,还是随机的?你确定在进行单元测试而不是集成测试吗?"NetworkController"类给我的印象是你可能正在进行更多的集成测试。如果是这种情况,而你又使用了公共类、提供程序、服务或存储介质(数据库、文件系统),那么一个方法引起的交互或状态变化可能会影响另一个测试方法的有效性。

我在第二次编辑中添加了TestCleanup方法。它只重置TimeProvider。没有测试初始化,我在测试方法中将所有内容作为局部变量进行初始化。之前我有测试初始化,在那里进行了SomeProvider类的初始化,但是没有起到帮助作用。 - user969153
NetworkController类只是更改PC的IP地址以模拟无互联网访问。该库的目的是在连接/断开连接和不同时间返回不同的状态 - 所有单元测试都试图模拟这一点 - 要么是时间变化,要么是网络变化,或者两者兼而有之。 - user969153

1
在异步/等待模式下运行测试时,会遇到一些延迟。看起来你的所有处理都发生在内存中。它们可能是逐个传递的,因为滞后时间很小。当以异步模式运行多个测试时,滞后时间足以导致时间结果的差异。
我之前在运行由NCrunch运行的NUnit测试时遇到过这种情况,在测试DateTime组件时。只要符合验收标准,你可以通过将验证/过期逻辑的范围缩小到秒级别而不是毫秒级别来减轻这种情况。从你的代码中无法确定逻辑驱动验证状态或过期日期,但我敢打赌异步滞后是并发运行时测试失败的根本原因。

0

两个测试都使用相同的静态TimeProvider,因此在清理中使用ResetDateTime等方法和在测试中使用TimeProvider.SetDateTime(today.AddYears(1));可能会产生干扰。此外,NetworkController似乎是一个静态资源,连接/断开它可能会干扰您的测试。

您可以通过以下几种方式解决这些问题:

  • 摆脱静态资源,改用实例
  • 锁定测试,使只能同时运行一个测试

除此之外,几乎每个测试框架都提供了不止Assert.IsTrue。您的框架没有提供Assert.AreEqual吗?这可以提高可读性。此外,在一个测试中有多个Assert时,建议使用自定义消息指示哪个测试失败(或者Assert是针对前置条件而非实际测试的)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接