繁体   English   中英

使用 Splat 为代码并行运行单元测试

[英]Run unit tests in parallel for code using Splat

我们使用 ReactiveUI 开发移动应用程序,因此使用 Splat 作为依赖注入框架。 在运行我们的单元测试时,我们决定让它们并行运行以提高 IDE 中的反馈速度。 我们注意到某些测试失败了,因为我们的 SUT 使用了 Splat,因此我们使用 splat 在测试中注入模拟。 我确信其他使用 Splat 的团队也发生过这种情况,所以我想知道是否有内置的方法来绕过这个障碍。

public interface IDependency
{
    void Invoke();
}

public class SUT1
{
    private IDependency dependency;
    public SUT1()
    {
        dependency = Locator.CurrentMutable.GetService<IDependency>();      
    }
    public void TestThis()
    {
        dependency.Invoke();
    }
}

public class SUT2
{
   private IDependency dependency;
    public SUT2()
    {
        dependency = Locator.CurrentMutable.GetService<IDependency>();      
    }
    public void TestThisToo()
    {
         dependency.Invoke();
    }
}

public class SUT1_Test()
{
    [Fact]
    public void TestSUT1()
    {
        var dependencyMock = new Mock<IDependency>();
        Locator.CurrentMutable.Register(() => dependencyMock.Object, typeof(IDependency));
        var sut = new SUT1();
        sut.TestThis();
        dependencyMock.Verify(x => x.Invoke(), Times.Once);
    }
}

public class SUT2_Test()
{
    [Fact]
    public void TestSUT2()
    {
        var dependencyMock = new Mock<IDependency>();
        Locator.CurrentMutable.Register(() => dependencyMock.Object, typeof(IDependency));
        var sut = new SUT2();
        sut.TestThisToo();
        dependencyMock.Verify(x => x.Invoke(), Times.Once);
    }
}

如果测试以在调用函数之前注入新模拟的方式运行,调用验证将变得一团糟。 禁用并行化使我们每次都能获得正确的结果,但代价是用于按顺序运行测试的时间。

免责声明:我不知道 SPLAT 是什么。 我写的所有内容都是关于 C#、DI、多线程、测试和常见陷阱的通用知识。 SPLAT 有可能以某种绝妙的方式解决它。 非零机会。 接近于零。


我猜你在服务定位器(反)模式中使用 DI。 我猜你的Locator.CurrentMutable.xxx是一个全局静态的方便的东西,你可以在任何地方访问它并向它请求任何东西。 所以,它被搞砸了多线程*)和/或测试。 时期。

引用自https://github.com/reactiveui/splat#service-location

Locator.Current 是一个静态变量,可以在启动时设置,以使 Splat 适应其他 DI/IoC 框架。 这通常是个坏主意。

所以,嗯,是的,它是静态的。 不好。

当您并行运行多个测试时,它们都会尝试为同一服务注册自己的模拟,并且它们必然会相交 如果你的 DI 东西是合理的,它会抛出异常。 似乎不是,所以最后一次注册可能获胜,所以测试 A 得到了测试 B 注册的模拟。这搞砸了验证,因为测试 A 从模拟 B 读取并且模拟记录的操作来自测试 B 而不是 A,所以从 A 验证失败。

这是正确的行为。

错误出在您的测试设置、测试运行或 DI 架构中。

如果你坚持使用服务定位器模式,至少让它成为非静态的。 每个测试都必须有自己的解析上下文。 如果它们是共享的,测试将相交并失败。

最简单的解决方案是删除服务定位器模式。 显然这是不可能的,因为你告诉我们你有一个很大的代码库。

另一种选择是使定位器去静电。 尝试使它可以是“上下文相关的”,以便每个测试都有自己独立的服务提供者/定位器实例 这将解决问题,因为注册将发生在不同的实例上,并且不会交叉和覆盖。

如果您的测试是普通的且内部是单线程的,则可以通过使Locator.CurrentMutableLocator线程静态或类似的东西(例如“异步上下文”或您认为更好的任何东西)来实现。 但是你应该只在测试中这样做,因为在实际应用程序中让它成为线程静态可能会破坏它,因为你的应用程序在设计时没有考虑到这一点。

最后,您可以尝试调整测试的运行方式,而不是摆弄代码、测试代码或服务提供商生命周期。 如果Locator.CurrentMutable是全局静态的并且必须保持这样,那么......

...那么您的测试不能在同一过程中并行运行(因为它们会相交)...

...但这并不妨碍您在单独的进程中运行它们。

获取文档,查看源代码,并为自己编写一个全新的 xUnit 运行器,它将:

  • 进行测试以运行
  • 创建 5-10-20-100 个工作进程(可配置?)
  • 分发测试以运行工作进程
  • 每个工作进程都得到他的部分测试
  • 每个工作进程一个接一个地运行他的测试,而不是并行

您可以将它们合并和出队,而不是预先分配,以确保不会出现这样一种情况:一个进程在他的 99 个测试中徘徊,因为一个测试花费了更长的时间,等等。由您决定。 最重要的是,如果您的服务定位器是全局的并且您在每个测试的基础上在其中注册模拟,则不要在单进程中并行化它们。

*) 是的,我知道互斥量/等等。 但它仍然是搞砸了,除非从消费者的角度来看它是不可变的,在这里它显然不是不可变的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM