如何才能正确地将相互依赖的测试方法单元化?

时间:2011-05-11 03:26:24

标签: c# unit-testing

考虑以下代码:

    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?惩戒?还有别的吗?

6 个答案:

答案 0 :(得分:7)

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

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

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

答案 1 :(得分:6)

你有两个选择:

  1. 使用反射或MSTest私有访问器类在测试期间获取和设置私有字段值。
  2. 只是不要担心它并测试暴露的行为,即使这意味着您的测试依赖于其他地方正在测试的其他属性或方法。
  3. 正如您可以从措辞中看出的那样,我的选择是#2 - 您应该测试暴露的行为。在您的情况下,您正在测试的暴露行为是:

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

    在这种情况下,相当明显的是它们几乎是一样的,所以我们实际上只有一个单元案例/暴露行为来测试。是的,这个单元测试包含两个不同的东西,但这并不重要 - 我们不是试图测试每个方法/属性的行为是否符合预期,而是我们想测试每个暴露的行为是否按预期工作。


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

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

    我们现在不仅要测试类的内部行为(如果实现更改测试失败,即使类正在按预期工作),但我们只是加倍数字我们正在编写的测试。

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

答案 2 :(得分:2)

当您使用Microsoft Unit Test Framework时,该框架会生成一个私有访问者类。这应该允许您访问您的私人类型。有关详细信息,请查看Microsoft的此页面:

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

特别是此部分:创建可以访问internal,private和friend方法的单元测试。

答案 3 :(得分:1)

我建议使用接口和/或虚拟方法和MOQ。这样你就可以调用你不想测试的方法,并让它们返回你想要的。

答案 4 :(得分:1)

这里有两种选择。

  1. 如前所述,一起测试功能。
  2. 修改您的课程,以便将其包装在测试工具中。
  3. 应使用单元测试定义测试工具,并公开验证功能是否正常工作所需的元素。您应该在单元测试中直接使用测试工具而不是类。

    
    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;
            }
        }
    }
    
    

    忘了提到另一个选项,即使用mocking进行依赖注入。如果您要模拟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);
        }
    }
    

答案 5 :(得分:1)

如果你想这样做,那就离开类进入构造函数(暂时忽略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>();
    }
}