简体   繁体   English

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

[英]How can I correctly unit test methods that depend on each other?

Consider this code: 考虑以下代码:

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

As you can see, AddComponent adds to the private _components variable. 如您所见, AddComponent添加到private _components变量。 But the only way to test this is happening is by using the indexer. 但测试这种情况的唯一方法是使用索引器。 That's fine, but in order to test the indexer I'd have to call AddComponent too! 这很好,但为了测试索引器,我还必须调用AddComponent

In other words, in the unit tests for the indexer and AddComponent , each test would have to call both of these methods. 换句话说,在索引器和AddComponent的单元测试中,每个测试都必须调用这两个方法。 It seems like this is creating unnecessary coupling. 这似乎是在创造不必要的耦合。 If there is a bug in the indexer, there is no reason for my TestAddComponent to fail. 如果索引器中存在错误,则我的TestAddComponent没有理由失败。

What is the best practice here? 这里的最佳做法是什么? Do I use reflection to get at _components ? 我是否使用反射来获取_components Mocking? 惩戒? Something else? 别的什么?

In my opinion, unit tests shouldn't do reflection to force it's goals . 在我看来, 单元测试不应该反思强迫它的目标 I think that in this kind of test, both should be tested toghether , in the same test. 我认为在这种测试中, 两者都应该在同一测试中进行测试。 But that is just a point of view. 但这只是一个观点。

However, you can make multiple tests, changing the order of the instructions. 但是,您可以进行多项测试,更改指令的顺序。 Try to add multiple, and the access the first, then the last, then one from the middle. 尝试添加多个,然后访问第一个,然后是最后一个,然后是中间的一个。 Each test, is one scenario, with different order, number of insertions. 每个测试都是一个场景,具有不同的顺序,插入次数。 You can even test exceptional states that must happen... for example, if you try to get something that was not inserted. 您甚至可以测试必须发生的异常状态...例如,如果您尝试获取未插入的内容。

I think that unit test exists to mimic usage, or to enforce specification . 我认为单元测试的存在是为了模仿使用或强制执行规范 Not to see if every single bit of the program is right, because that kills flexibility. 不要看是否该程序的每一位都是正确的,因为这会杀死灵活性。

Well you have 2 options: 那你有两个选择:

  1. Use reflection or the MSTest private accessor classes to get and set private field values during your test. 使用反射或MSTest私有访问器类在测试期间获取和设置私有字段值。
  2. Just don't worry about it and test the exposed behaviour, even if that means your test depends on other properties or method that are being tested elsewhere. 只是不要担心它并测试暴露的行为,即使这意味着您的测试依赖于其他地方正在测试的其他属性或方法。

As you can probably tell from the wording, my choice would be with #2 - You should test the exposed behaviour . 你可以从措辞中看出,我的选择是#2 - 你应该测试暴露的行为 In your case the exposed behaviour that you are testing is: 在您的情况下,您正在测试的暴露行为是:

  • If I use AddComponent then the added component should be accessible through the indexer 如果我使用AddComponent那么添加的组件应该可以通过索引器访问
  • If I use the indexer, I should be able to access any components that were added through AddComponent 如果我使用索引器,我应该能够访问通过AddComponent添加的任何组件

In this case its fairly obvious that these are pretty much the same thing, so we only really have one unit case / exposed behaviour to test here. 在这种情况下,相当明显的是这些几乎是相同的,所以我们实际上只有一个单元案例/暴露行为来测试。 Yes this unit test covers two different things, but that shouldn't really matter - we aren't trying to test that each method / property behaves as expected, rather we want to test that each exposed behaviour works as expected. 是的,这个单元测试包含两个不同的东西,但这并不重要 - 我们不是试图测试每个方法/属性的行为是否符合预期,而是我们想测试每个暴露的行为是否按预期工作。


As an alternative, suppose that we go for option 1 and used private reflection to check the state of _components ourselves. 作为替代方案,假设我们选择选项1并使用私有反射来检查_components的状态。 In this case the bevahour that we are actually testing is: 在这种情况下,我们实际测试的bevahour是:

  • If I use AddComponent then the added component should be added to _components 如果我使用AddComponent将添加的组件添加到_components
  • If I use the indexer, I should be able to access any components that are in _components 如果我使用索引器,我应该能够访问_components任何组件

Not only are we now testing the internal behaviour of the class (so that if the implementation changes the tests fail, even if the class is working as expected), but we have just doubled the number of tests we are writing. 我们现在不仅测试类的内部行为(因此,如果实现更改测试失败,即使类按预期工作),但我们只是我们编写的测试数量增加一倍

On top of that, by increasing the complexity of our tests we are increasing the chance that the tests themselves have a bug - for example what if we made a mistake and in test 2. we checked some completely different private field? 最重要的是,通过增加测试的复杂性,我们增加了测试本身存在错误的可能性 - 例如,如果我们犯了错误并且在测试2中,我们检查了一些完全不同的私有字段? In this case not only have we made more work for ourselves, but we aren't even testing the actual behaviour that we want to test! 在这种情况下,我们不仅为自己做了更多工作,而且我们甚至没有测试我们想要测试的实际行为!

When you're using Microsoft Unit Test Framework, the framework generates a private accessor class. 当您使用Microsoft Unit Test Framework时,该框架会生成一个私有访问器类。 This should allow you to access your private types. 这应该允许您访问您的私人类型。 Take a look at this page from Microsoft for more info: 有关详细信息,请查看Microsoft的此页面:

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

Specially this section: Create unit tests that can access internal, private, and friend methods. 特别是本节:创建可以访问internal,private和friend方法的单元测试。

May I suggest using interfaces and or virtual methods and MOQ. 我可以建议使用接口和/或虚拟方法和MOQ。 That way you can MOQ the calls to the methods you are not wanting to test and make them return what you want. 这样你就可以调用你不想测试的方法,并让它们返回你想要的。

There's two options here. 这里有两个选择。

  1. Test the functionality together as has already been mentioned. 如前所述,一起测试功能。
  2. Modify your class so you can wrap it in a test harness. 修改您的类,以便将其包装在测试工具中。

Your test harness should be defined with your unit tests and expose the elements which you need to verify that the functionality worked correctly. 应使用单元测试定义测试工具,并公开验证功能是否正常工作所需的元素。 You should use your test harness instead of the class directly in your unit tests. 您应该在单元测试中直接使用测试工具而不是类。


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


Forgot to mention the other option which is dependency injection with mocking. 忘了提到另一个选项,即使用mocking进行依赖注入。 If you were to mock IDictionary then you could verify your test. 如果您要模拟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);
    }
}

If you want to do it this way, move out of the class into the constructor (ignoring IoC frameworks for the moment) and use the interface to reference the dependency: 如果你想这样做,那就离开类进入构造函数(暂时忽略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);
    }
}

Now you can mock the dependency and verify the interactions. 现在,您可以模拟依赖关系并验证交互。

However, given the lack of added behavior, I believe the truly practical approach is expose the member directly and throw away the aggregated object's accessors: 但是,由于缺乏添加的行为,我认为真正实用的方法是直接暴露成员并丢弃聚合对象的访问者:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在异步方法的单元测试中测试ConfigureAwait(false)? - How can I test for ConfigureAwait(false) in a unit test for async methods? 在MSTest中,如何指定某些测试方法不能彼此并行运行? - In MSTest, how can I specify that certain test methods cannot be run in parallel with each other? 如何对我的 DI 配置正确进行单元测试 - How can I unit test that my DI is configured correctly 依赖于被调用接口的其他成员的单元测试方法 - Unit testing methods which depend on other member of the interface being called 如何简化相互依赖的绑定? - How to simplify bindings that depend on each other? 如何对这个(正确)抛出异常的异步方法进行单元测试? - How can I unit test this async method which (correctly) throws an exception? 如何使用 InMemory 数据库检查单元测试中正确添加的记录 - How can I use InMemory database to check records added correctly in a unit test 使用其他方法的方法的单元测试 - unit test for method that use other methods 如何在类中模拟依赖两个方法 - how can mocking two methods on depend in the class 我将如何在 C# 中对这些序列化/反序列化方法进行单元测试? - How would I Unit Test these Serialization/Deserialization methods in C#?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM