【问题标题】:Unit testing class with private constructor created from factory method具有从工厂方法创建的私有构造函数的单元测试类
【发布时间】:2014-05-30 15:09:20
【问题描述】:

我知道有人提出/回答了非常相似的问题,但我遇到了一个我无法解决的特殊情况。

我们正在创建一个用于其他应用程序的库,该库以用户实例化和使用的主库类为中心。这个类有多种依赖,部分设计要求要求我们不希望库的用户处理创建依赖对象图或处理初始化任务,因此我们将类构造函数设为私有,并具有他们从静态方法创建一个实例,该方法又创建一个类的实例并使用 IoC 容器解决依赖关系。像这样的:

    public static ILibrary CreateInstance()
    {

        Library lib = new Library(
            IoC.Instance.Resolve<IDependency1>(),
            IoC.Instance.Resolve<IDependency2>(),
            // etc
            );

        lib.Initialize();

        return lib;
    }

    private Library(IDependency1, IDependency2, etc)
    {
        // some ctor setup here
    }

测试这个的核心问题是 CreateInstance() 方法调用 IoC 容器的静态实例来处理依赖解析,这会阻止我们将模拟依赖注入到对象图中。

本着“如果你不知道如何测试它,你可能有设计缺陷”的精神,我想知道是否有人可以指出解决这个问题的正确方法。

【问题讨论】:

  • 我的头脑:创建一个内部构造函数,让您的单元测试项目通过“InternalsVisibleTo”(仔细检查该名称)属性访问内部。如果您感兴趣,请搜索该方法。
  • 你在哪里定义你的依赖是如何解决的?
  • @IanP IoC 类有一个静态构造函数,它调用私有 Initialize() 方法,我们在其中注册我们的依赖项。
  • 嗯。那么它们是在代码中还是在 dependencies.config 文件中定义的?
  • 我会切入正题:) 如果您在配置文件中定义依赖项,是否可以在测试时使用不同的配置?

标签: c# unit-testing


【解决方案1】:

您至少有 2 个选项:


public构造函数

您提到您不希望您的库的用户必须处理初始化和对象图的创建。这是将此类初始化内容隐藏在工厂后面的一个很好的理由 - 但这并不妨碍您允许用户处理这些细节。

您的测试可以被视为您的代码的消费者。如果测试希望能够以某种方式(例如,通过依赖注入)配置对象,那么其他消费者很可能会分享这种兴趣。

通过提供工厂方法,您可以让消费者能够创建库,而不必担心编写其对象图。

通过提供public 构造函数,您可以选择消费者使用特定对象图创建库。


internal构造函数

一种常用来测试共享库的私有组件的方法是expose relevant components specifically to the test project

不要将构造函数指定为private,而是将其标记为internal。然后,修改共享库的 AssemblyInfo 以包含以下程序集属性:

    [assembly: InternalsVisibleTo("MyLibraryTestProject")]

标记为internal 的类和方法通常只对定义它们的项目可见。上述更改明确允许您的测试项目查看(和测试)它们。

【讨论】:

  • 这(内部)是我用来测试外部不可见的辅助类的方法,在这种情况下它似乎也是一种直接的测试方法。
  • 啊,我已经根据@user414076 评论尝试过这个,但无法使其工作,因为我没有将私有更改为内部。这可能是要走的路,谢谢。
【解决方案2】:

这是我想出的方法,虽然有效,但并不完全理想。我仍然很想听听其他想法。

在工厂方法中,我没有调用静态 IoC.Instance,而是在库本身上创建了一个静态属性,如果未设置,它将返回库的 IoC 容器,或者允许我用一个满载的容器覆盖它嘲笑。基本上:

private static Container _container;
public static Container Container
{
    get { if (_container ==null) return IoC.Instance; return _container; }
    set { _container = value; }
}

那么在单元测试中,我只是将容器与用于测试的容器交换。

我唯一不喜欢的是它暴露了一个我不希望库消费者使用的公共属性,这使 API 设计有点混乱。但现在我可能只是在挑剔。

【讨论】:

    【解决方案3】:

    指向像这样初始化库:

        Library lib = new Library(
            IoC.Instance.Resolve<IDependency1>(),
            IoC.Instance.Resolve<IDependency2>(),
            // etc
            );
    

    依赖注入是否使您能够在其中获取测试依赖项。配置您的 IoC 对象以提供正确的依赖关系。似乎您必须在测试时制作一个 Mock IoC 对象,使用它来注入测试依赖项。您可以使用模拟框架(例如 Fakes、Mock 等)来简化此操作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-07-24
      • 2012-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-03
      • 1970-01-01
      相关资源
      最近更新 更多