Как я учитываю код для облегчения тестирования? - PullRequest
4 голосов
/ 01 декабря 2009

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

class Car
{
private:
   Engine m_engine;

public:
   Car();
   // Rest of the car
}

Я предложил следующие решения, чтобы сделать приведенный выше код тестируемым.

  1. Изменение конструктора автомобиля, чтобы в качестве параметра использовался Engine. Затем издевайтесь над двигателем и проведите тестирование. Но, если у меня нет разных типов двигателей, кажется неуместным параметрировать конструктор только для того, чтобы сделать его тестируемым.

  2. Используя установщик, а затем передайте имитирующий Двигатель установщику. Тот же поток, что и выше.

  3. Сначала тестирование двигателя, а затем тестирование автомобиля с проверенным двигателем (или с использованием заглушки двигателя).

Какие альтернативы я должен сделать из приведенного выше кода для тестирования? Каковы сильные и слабые стороны каждого метода?

Ответы [ 8 ]

7 голосов
/ 01 декабря 2009

Посмотрите на это с другой точки зрения (тест-ориентированной разработки): код, который легко протестировать, прост в использовании. Написание модульных тестов - это на самом деле вы тестируете «открытый интерфейс» вашего кода. Если это трудно проверить, это потому, что у вас есть некоторые зависимости, которые усложняют его. Вам действительно нужны отношения сдерживания, или ассоциативные отношения имеют больше смысла?

В вашем случае я лично думаю, что было бы более тестируемым передать Engine в конструкторе, поэтому я бы реорганизовал конструктор, как в вашем предложении # 1. Вы можете протестировать Двигатель в одном тестовом наборе и предоставить пробный Двигатель для тестирования Автомобиля в другом тестовом наборе. Тестирование теперь легко, а это значит, что интерфейс прост в использовании. Это хорошая вещь.

Теперь подумайте, как бы вы использовали эту реализацию в реальном проекте. Вы создадите класс CarFactory, а фабрика создаст Engine и поместит его в автомобиль, прежде чем доставить его вам. (Также обратите внимание, как это в конечном итоге более близко моделирует реальный мир автомобилей, двигателей и заводов, но я отвлекся.)

Следовательно, ответом TDD будет рефакторинг кода для получения указателя Engine на конструкторе.

5 голосов
/ 01 декабря 2009

Если у вас есть только один тип Engine, почему вы пытаетесь сделать его новым объектом? Если вы не планируете менять движки, не создавайте другой слой абстракции. Просто сделайте двигатель частью машины.

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

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

2 голосов
/ 01 декабря 2009

Misko Hevery часто пишет на эту тему. Вот презентация от октября 2009 года. Он утверждает, что граф зависимостей должен быть явным в конструкторе.

кажется неуместным параметризировать конструктор только для того, чтобы сделать его тестируемым

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

2 голосов
/ 01 декабря 2009

Реальный вопрос в том, каковы требования? Если цель состоит в том, чтобы просто реализовать объект «Автомобиль», то ему даже не нужен двигатель.

Тесты всегда должны быть связаны с требованиями. Любая объектная модель будет некоторым обобщением реальности, поэтому вопрос в том, какие аспекты должны быть представлены.

Как только вы соберете свои требования, вы должны написать свои тесты на общем высоком уровне. Затем проект ОО должен быть выполнен таким образом, чтобы эти тесты могли быть реализованы.

2 голосов
/ 01 декабря 2009

Вариант 1, как правило, правильный.

Имея полный контроль над двигателем, который вы даете машине, вы можете хорошо проверить машину.

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

Наличие этого в конструкторе действительно дает понять, что автомобиль зависит от двигателя для работы. Используйте его со структурой внедрения зависимостей, и проблема с конструктором на самом деле не является проблемой.

2 голосов
/ 01 декабря 2009

Модульное тестирование, как следует из названия, касается тестирования модулей, чтобы подтвердить, что они работают в соответствии с предписаниями. Это означает, что вы должны проверить двигатель отдельно.

Системное тестирование или интеграционное тестирование - это проверка того, что все они «склеиваются» правильно.

Конечно, это сложнее, чем просто это, но оно должно указать вам правильное направление.

1 голос
/ 01 декабря 2009

Одна возможность в C ++ - использовать дружбу:

class Car
{
private: //for unit testing
    friend class TestCar; //this class is the unit test suite for the Car class
    Car(Engine* mockEngine); //this constructor is only used by the TestCar class
private:
    Engine* m_engine;
public:
    Car();
    // Rest of the car
};

Вторая возможность для реализации конструктора - использовать глобальный / статический метод, например, следующим образом, и вы можете изменить реализацию этого метода, либо через некоторый файл конфигурации, либо связав (возможно, динамическое связывание) с другим Версии этого метода:

Car::Car()
{
    m_engine = Engine::create();
}
0 голосов
/ 03 декабря 2009

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

Автомобильные тесты на самом деле не должны заботиться о том, что делает Двигатель, если они правильно реагируют на результаты вызовов в Двигатель. Затем используйте поддельный Двигатель для проверки, чтобы вы могли контролировать сигналы, посылаемые автомобилю, и вам будет хорошо идти.

...