如何正确单元相互依赖的测试方法

本文关键字:依赖 测试方法 何正确 单元 | 更新日期: 2023-09-27 17:55:52

请考虑以下代码:

    private readonly Dictionary<Type, Component> _components = new Dictionary<Type, Component>();
    public Component this [Type type]
    {
        get
        {
            Component component;
            _components.TryGetValue(type, out component);
            return component;
        }
    }
    public void AddComponent(Component component)
    {
        _components.Add(component.GetType(), component);
    }

如您所见,AddComponent添加到私有_components变量中。但是,测试这种情况的唯一方法是使用索引器。这很好,但为了测试索引器,我也必须调用AddComponent

换句话说,在索引器和AddComponent的单元测试中,每个测试都必须调用这两个方法。这似乎正在产生不必要的耦合。如果索引器中存在错误,则我的TestAddComponent没有理由失败。

这里的最佳实践是什么?我用反射来获得_components吗?嘲笑?别的?

如何正确单元相互依赖的测试方法

在我看来,单元测试不应该做反思来强迫它的目标。我认为在这种测试中,两者都应该在同一个测试中进行测试。但这只是一个观点。

但是,您可以进行多个测试,更改说明的顺序。尝试添加多个,访问第一个,然后是最后一个,然后是一个中间。每个测试都是一个场景,具有不同的插入顺序和次数。您甚至可以测试必须发生的异常状态...例如,如果您尝试获取未插入的内容。

我认为单元测试的存在是为了模仿用法或强制执行规范。不要看程序的每一点是否正确,因为这会扼杀灵活性。

好吧,您有 2 个选择:

  1. 使用反射或 MSTest 专用访问器类在测试期间获取和设置私有字段值。
  2. 只是不要担心它并测试暴露的行为,即使这意味着您的测试依赖于其他地方正在测试的其他属性或方法。

正如您可能从措辞中可以看出的那样,我的选择是#2 - 您应该测试暴露的行为。 在您的情况下,您正在测试的暴露行为是:

  • 如果我使用AddComponent那么添加的组件应该可以通过索引器访问
  • 如果我使用索引器,我应该能够访问通过AddComponent添加的任何组件

在这种情况下,很明显,这些几乎是一回事,所以我们实际上只有一个单位案例/暴露的行为在这里进行测试。 是的,这个单元测试涵盖了两个不同的东西,但这并不重要 - 我们不是在尝试测试每个方法/属性的行为是否符合预期,而是要测试每个公开的行为是否按预期工作。


作为替代方案,假设我们选择选项 1 并使用私人反思来检查自己的_components状态。 在这种情况下,我们实际测试的 bevahour 是:

  • 如果我使用AddComponent那么添加的组件应该添加到_components
  • 如果我使用索引器,我应该能够访问_components中的任何组件

我们现在不仅要测试类的内部行为(这样,如果实现更改,即使类按预期工作,测试也会失败),而且我们编写的测试数量刚刚翻了一番

最重要的是,通过增加测试的复杂性,我们增加了测试本身出现错误的可能性 - 例如,如果我们犯了错误,在测试 2 中,我们检查了一些完全不同的私人领域? 在这种情况下,我们不仅为自己做了更多的工作,而且我们甚至没有测试我们想要测试的实际行为!

使用单元测试框架Microsoft框架时,框架会生成一个私有访问器类。这应该允许您访问私有类型。查看Microsoft的此页面以获取更多信息:

http://msdn.microsoft.com/en-us/library/dd293546.aspx

特别是本节:创建可以访问内部、私有和友元方法的单元测试。

我可以建议使用接口和/或虚拟方法和最小起订量。这样,您可以对不想测试的方法的调用进行最小起订量,并使它们返回所需的内容。

这里有两个选项。

  1. 如前所述,一起测试功能。
  2. 修改类,以便将其包装在测试工具中。

测试工具应使用单元测试进行定义,并公开验证功能是否正常工作所需的元素。应直接在单元测试中使用测试工具而不是类。


public class MyClass
{    
    protected readonly Dictionary<Type, Component> _components = new Dictionary<Type, Component>();
    public Component this [Type type]
    {
        get
        {
            Component component;
            _components.TryGetValue(type, out component);
            return component;
        }
    }
    public void AddComponent(Component component)
    {
        _components.Add(component.GetType(), component);
    }
}
public class MyClassTestHarness : MyClass
{
    public Dictionary<Type, Component> Components
    {
        get
        {
            return _components;
        }
    }
}

忘了提到另一个选项,即带有嘲笑的依赖注入。如果你要模拟IDictionary,那么你可以验证你的测试。


public class MyClass
{    
    protected IDictionary _components;
    public MyClass()
    {
         _components = new Dictionary();
    }
    public MyClass(IDictionary components)
    {
         _components = components;
    }
    public Component this [Type type]
    {
        get
        {
            Component component;
            _components.TryGetValue(type, out component);
            return component;
        }
    }
    public void AddComponent(Component component)
    {
        _components.Add(component.GetType(), component);
    }
}

如果要这样做,请从类中移出到构造函数中(暂时忽略 IoC 框架)并使用接口引用依赖项:

class ComponentManager
{
    private readonly IDictionary<Type, Component> _components;
    public ComponentManager()
        : this(new Dictionary<Type, Component>())
    { }
    public ComponentManager(IDictionary<Type, Component> components)
    {
        _components = components;
    }
    public Component this[Type type]
    {
        get
        {
            Component component;
            _components.TryGetValue(type, out component);
            return component;
        }
    }
    public void AddComponent(Component component)
    {
        _components.Add(component.GetType(), component);
    }
}

现在,您可以模拟依赖项并验证交互。

但是,鉴于缺乏添加的行为,我认为真正实用的方法是直接公开成员并丢弃聚合对象的访问器:

class ComponentManager
{
    public Dictionary<Type, Component> Components { get; private set; }
    public ComponentManager()
    {
        Components = new Dictionary<Type, Component>();
    }
}