【问题标题】:Unit testing C#/.NET classes which make use of static variables (unit test process segregation)使用静态变量的单元测试 C#/.NET 类(单元测试过程隔离)
【发布时间】:2014-06-20 20:28:54
【问题描述】:

我有一个代码库,它在许多有意义的情况下使用静态变量,例如标记某些东西自启动以来已经运行过一次,等等。

当然,这可能会导致单元测试出现问题,因此突然顺序很重要,并且对此类方法的测试结果可能取决于之前是否已命中其他代码等。我对 TestTools.UnitTesting 的理解是每当我运行一组单元测试时,同一个项目中的任何一个都在同一个进程中运行,因此从测试到测试都保持任何静态状态,而单元测试项目边界也意味着一个进程边界,因此,如果我运行来自项目 A 的 3 个测试,然后来自项目 B 的第四个测试,状态保持从 1>2>3(以它们运行的​​任何顺序),但随后 4 是处女并且任何静态状态都是默认的。

所以现在我的问题是两个:

1) 我的评估是否正确,即当测试在组中运行(全部运行或选择运行)时,单元测试项目与流程具有 1:1 的关系,还是我遗漏了更多细微差别?

2) 无论如何,如果我有一个测试肯定需要它使用和测试的自定义对象的新鲜、默认静态状态,我有没有比给它自己的测试项目更优雅的创建它的选项?

【问题讨论】:

  • 附带说明,您可以让测试加载 AppDomain,然后在 AppDomain 中运行测试。可能的优点是静态变量是每个 AppDomain 的。缺点是工作量很大。
  • 非常有用的信息,与下面 mike z 的回答非常相似。

标签: c# .net unit-testing


【解决方案1】:

静态实际上不是每个进程,而是每个应用程序域,由 AppDomain 类表示。一个进程可以有多个 AppDomain。 AppDomains 有自己的静态,可以为部分受信任的代码提供沙盒,并且可以卸载允许相同程序集的较新版本进行热交换,而无需重新启动应用程序。

您的测试运行程序可能会为每个测试程序集创建一个新的 AppDomain,以便每个程序集都有自己的静态变量。您可以创建一个 AppDomain 来即时执行相同的操作。这对于纯单元测试通常不是很好,但我不得不使用“粗鲁”的库来执行各种无法清除或重置的静态初始化和缓存。在这类集成场景中,它非常有用。

您可以使用这个助手来运行一个简单的委托:

public static class AppDomainHelper
{
    public static void Run(Action action)
    {
        var domain = AppDomain.CreateDomain("test domain");
        try
        {
            domain.DoCallBack(new CrossAppDomainDelegate(action));
        }
        finally
        {
            AppDomain.Unload(domain);
        }
    }
}

需要注意的是,传递给 Run 的委托 action 不能有任何捕获的变量(如来自 lambda)。这不起作用,因为编译器会生成一个不可序列化的隐藏类,因此它不能通过 AppDomain 边界。

【讨论】:

    【解决方案2】:

    据我所知,您的评估是正确的——程序集是在测试过程开始时加载的,并且在整个测试过程中都会保持任何静态状态。

    您应该始终从“新鲜”状态开始。单元测试应该能够以任何顺序运行,没有任何依赖关系。原因是您的测试需要可靠——测试失败的原因只有一个:它测试的代码发生了变化。如果您的测试依赖于其他测试,那么您很容易以一个测试失败并“破坏链条”而告终,从而导致其他十几个测试失败。

    您可以使用TestInitialize 属性定义一个方法,该方法将在每次将状态重置为基线的测试之前运行。

    实现此功能的另一种方法是将静态状态包装到单例中,然后将“后门”放入单例中,以便您可以注入单例类的实例,允许您将应用程序的状态配置为安排考试的一部分。

    【讨论】:

    • TestInitialize 对于为服务器代码的单元测试设置全新的数据库也很有用。
    • 当然,我确实使用 TestInitialize 在测试之间重置数据库等。我也可以用它来重置任何静态状态,但这首先触及了问题的核心:当然,我可以在我的代码库中的任何地方列出并重置所有静态状态,但这很可能是不合理的工作量,并且现在和以后都非常容易出错。
    【解决方案3】:

    如果您不是要测试全局状态,而是要测试从全局状态中获得的值,您可以解决它。

    考虑这个使用一些静态属性的简单类定义。

    public class Foo
    {
       public int Bar(int baz)
       {
           return baz + GlobalState.StaticValue;
       }
    }
    

    你可以这样重构它。

    public class Foo
    {
        public virtual int GetGlobalStaticValue
        {
            return GlobalState.StaticValue;
        }
    
        public virtual int Bar(int baz)
        {
            return baz + this.GetGlobalStaticValue();
        }
    }
    

    我在方法定义中添加了virtuals,因为这是 Rhino Mocks 特有的,但您明白了 - 在实时运行时,您的类将像现在一样拉取全局状态,但它为您提供了模拟的钩子将在您的测试场景中返回的值。

    【讨论】:

    • 当然有用,但在我的情况下不太好用(代码库很大而且很乱,而且我的许多“单元测试”实际上更像是集成测试。遗憾的是,测试不是预想应该是的。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-07
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 2018-08-23
    相关资源
    最近更新 更多