1

我有这个工厂类,我想正确测试它。假设我有一个抽象类,它有很多孩子(继承)。

正如您在我的 Factory 类中看到的 BuildChild 方法,我希望能够在运行时创建子类的实例。我必须能够在运行时创建此实例,因为在运行之前不会知道类型。而且,我不能在这个项目中使用 Unity(如果是这样,我不会问如何实现这一点)。

这是我要测试的工厂类:

public class Factory
{
    public AnAbstractClass BuildChild(Type childType, object parameter)
    {
        AnAbstractClass child = (AnAbstractClass) Activator.CreateInstance(childType);
        child.Initialize(parameter);
        return child;
    }
}

为了测试这一点,我想找到一种方法来模拟 Activator.CreateInstance 来返回我自己的子类的模拟对象。我怎样才能做到这一点?或者,如果您有更好的方法可以在不使用 Activator.CreateInstance(和 Unity)的情况下做到这一点,如果它更容易测试和模拟,我会接受它!

我目前正在使用 Moq 创建我的模拟,但由于 Activator.CreateInstance 是来自静态类的静态方法,我无法弄清楚如何做到这一点(我已经知道 Moq 只能创建对象的模拟实例)。

我查看了来自 Microsoft 的 Fakes,但没有成功(我在理解它的工作原理和找到一些解释清楚的示例时遇到了一些困难)。

请帮我!

编辑:

我需要模拟 Activator.CreateInstance 因为我想强制此方法返回另一个模拟对象。我想要的正确方法只是存根这个方法(而不是模拟它)。

所以当我像这样测试 BuildChild 时:

[TestMethod]
public void TestBuildChild()
{
    var mockChildClass = new Mock(AChildClass);
    // TODO: Stub/Mock Activator.CreateInstance to return mockChildClass when called with "type" and "parameter" as follow.
    var type = typeof(AChildClass);
    var parameter = "A parameter";

    var child = this._factory.BuildChild(type, parameters);
}

使用类型和参数调用的 Activator.CreateInstance 将返回我的模拟对象,而不是创建真实子类的新实例(尚未实现)。

4

3 回答 3

4

好吧,我很想说这不是你需要模拟的东西,因为它应该被信任接受测试,但我猜如果类型来自外部源库,那么你可能会遇到问题......话虽如此,实现这一点的唯一方法是包装 Activator 使其不是静态类。

像这样的东西:

public ActivatorWrapper
{
    public virtual object CreateInstance(Type type)
    {
        return Activator.CreateInstance(type);
    }
}
于 2013-06-28T22:01:25.527 回答
1

您将不得不引入一个接口,该接口公开一种从该类型创建实例的方法。让您的类在其构造函数中实现接口。

然后你可以嘲笑它。

然后你有一个实现,它只委托给你在生产中使用的 Activator.CreateInstance。

也就是说,你为什么需要嘲笑这个?为什么您的测试不能只检查它是否返回方法调用中指定的类型?

从您的编辑开始,您为什么不能模拟对 BuildChild 的工厂调用而不是工厂内部的调用。这似乎是您要模拟的依赖项。

似乎您要么想测试工厂是否返回正确的类型,而您不需要对其进行任何模拟,或者您想为工厂引入一个接口并对其进行模拟。

我认为想要模拟 Activator.CreateInstance 是您的代码告诉您您的设计不太正确。

您可以测试您的工厂实现,而无需实现 AnAbstractClass 或无需模拟我认为的任何类似的东西:

创建一个测试实现:

public class TestAnAbstractClass : AnAbstractClass
{
     public object ConstructorParameter;

     public TestAnAbstractClass(object constructorParameter)
     {
          this.constructorParameter = constructorParameter;
     }
}

然后在你的测试中用这个打电话给你的工厂:

[TestMethod]
public void TestBuildChild()
{
    var type = typeof(TestAnAbstractClass);
    var parameter = "A parameter";

    var child =(TestAnAbstractClass) this._factory.BuildChild(type, parameters);
    Assert.That(child.ConstructorParameter, Is.EqualTo(parameter));
}

然后您正在测试工厂的实际功能,即使实现更改测试也不需要,并且您测试所有代码。

于 2013-06-28T22:05:41.817 回答
0

您可以尝试使用如下示例的环境上下文;

    public static class SystemActivator
    {
        private static Dictionary<Type, object> _mockObjects;

        private static Dictionary<Type, object> MockObjects
        {
            get { return _mockObjects; }
            set
            {
                if (value.Any(keyValuePair => keyValuePair.Value.GetType() != keyValuePair.Key))
                {
                    throw new InvalidCastException("object is not of the correct type");
                }
                _mockObjects = value;
            }
        }

        [Conditional("DEBUG")]
        public static void SetMockObjects(Dictionary<Type, object> mockObjects)
        {
            MockObjects = mockObjects;
        }

        public static void Reset()
        {
            MockObjects = null;
        }

        public static object CreateInstance(Type type)
        {
            if (MockObjects != null)
            {
                return MockObjects.ContainsKey(type) ? MockObjects[type] : Activator.CreateInstance(type);
            }
            return Activator.CreateInstance(type);
        }
    }

这是一个非常基础的课程,可以让您了解可以使用的方法。测试时,您可以设置 MockObjects 字典,将类型与您想要返回的实例配对(即您的模拟对象)。然后,不要在您正在构建的类中使用 Activator,而是使用 SystemActivator。

这个类的其他优点是你可以在某种程度上对它进行单元测试,例如测试它在设置时返回你的模拟,而不是在没有时返回激活器实例。

Activator 永远不会返回与请求的类型不同的实例,SystemActivator 应该模仿这种行为,这就是我包含异常的原因。您还可以考虑引发其他错误(例如,激活器无法使用本示例中显示的 CreateInstance 重载创建字符串,但如果您将 MockObject 设置为返回字符串值,它将 - 不允许发生这种情况)

这种方法有危险,你永远不应该尝试从生产代码中设置 MockObjects。为了减少这种危险,我为该方法提供了 [Conditional("DEBUG")] 属性。这不会阻止开发人员尝试在生产代码中设置 MockObjects,但会导致发布构建中断。

此外,在任何使用 SystemActivator 的测试类中,您都需要包含一个用于重置模拟对象的拆卸方法,否则存在创建测试依赖项的危险。

于 2016-10-05T13:04:10.793 回答