Как правильно настроить методы юнит-тестирования, которые зависят друг от друга? - PullRequest
7 голосов
/ 11 мая 2011

Рассмотрим этот код:

    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 ]

7 голосов
/ 11 мая 2011

По моему мнению, модульные тесты не должны отражать свои цели .Я думаю, что в этом виде теста оба должны быть проверены вместе в том же тесте.Но это всего лишь точка зрения.

Однако вы можете сделать несколько тестов, изменив порядок инструкций.Попробуйте добавить несколько, и получите доступ сначала к одному, затем к последнему, а затем к одному из середины.Каждый тест - это один сценарий, с разным порядком, количеством вставок.Вы даже можете протестировать исключительные состояния, которые должны произойти ... например, если вы попытаетесь получить что-то, что не было вставлено.

Я думаю, что модульный тест существует для имитации использования или для обеспечения спецификации .Не видеть, правильный ли каждый бит программы, потому что это убивает гибкость.

6 голосов
/ 11 мая 2011

Ну, у вас есть 2 варианта:

  1. Используйте отражение или классы закрытого доступа MSTest для получения и установки значений закрытого поля во время теста.
  2. Просто не беспокойтесь об этоми протестируйте открытое поведение, даже если это означает, что ваш тест зависит от других свойств или метода, которые тестируются в другом месте.

Как вы, вероятно, можете сказать из формулировки, мой выбор будет с # 2 -Вы должны проверить выставленное поведение .В вашем случае тестируемое поведение, которое вы тестируете:

  • Если я использую AddComponent, то добавленный компонент должен быть доступен через индексатор
  • Если я использую индексатор, ядолжен иметь возможность доступа к любым компонентам, которые были добавлены с помощью AddComponent

. В этом случае совершенно очевидно, что это почти одно и то же, поэтому у нас действительно только один случай блока / открытое поведение дляпроверить здесь.Да, этот модульный тест охватывает две разные вещи, но это не должно иметь большого значения - мы не пытаемся проверить, что каждый метод / свойство ведет себя так, как ожидалось, скорее мы хотим проверить, что каждое уязвимое поведение работает, как ожидалось.


В качестве альтернативы предположим, что мы выбрали вариант 1 и использовали личное отражение, чтобы сами проверить состояние _components.В этом случае bevahour, который мы на самом деле тестируем:

  • Если я использую AddComponent, то добавленный компонент должен быть добавлен к _components
  • Если я использую индексатор,Я должен иметь возможность доступа к любым компонентам, которые находятся в _components

. Мы не только теперь тестируем внутреннее поведение класса (так что, если реализация изменится, тесты не пройдут, даже если классработает как положено), но мы только удвоили количество тестов, которые мы пишем.

Кроме того, увеличивая сложность наших тестов, мы увеличиваем вероятность того, что сами тесты есть ошибка - например, что если мы допустили ошибку, а в тесте 2. мы проверили какое-то совершенно другое закрытое поле?В этом случае мы не только сделали больше для себя, но даже не тестируем то поведение, которое хотим протестировать!

2 голосов
/ 11 мая 2011

Когда вы используете Microsoft Unit Test Framework, среда генерирует закрытый класс доступа.Это должно позволить вам получить доступ к вашим личным типам.Взгляните на эту страницу от Microsoft для получения дополнительной информации:

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

Специально для этого раздела: создание модульных тестов с доступом к внутренним, закрытым и дружественным методам.

1 голос
/ 11 мая 2011

Если вы хотите сделать это таким образом, перейдите из класса в конструктор (на данный момент игнорируя платформы 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>();
    }
}
1 голос
/ 11 мая 2011

Здесь есть два варианта.

  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);
    }
}
1 голос
/ 11 мая 2011

Могу ли я предложить использовать интерфейсы и / или виртуальные методы и MOQ. Таким образом, вы можете MOQ вызывать методы, которые вы не хотите тестировать, и заставлять их возвращать то, что вы хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...