Подтвердить что ты не робот

Как писать тесты без стольких издевок?

Я серьезный сторонник надлежащего тестирования Driven Design или Behavior Driven Design, и я люблю писать тесты. Тем не менее, я продолжаю кодировать себя в угол, где мне нужно использовать 3-5 макетов в конкретном тестовом примере для одного класса. Независимо от того, в каком направлении я начинаю, сверху вниз или снизу вверх, я получаю дизайн, в котором требуется как минимум три сотрудника с самого высокого уровня абстракции.

Может кто-нибудь дать хороший совет, как избежать этой ловушки?

Здесь типичный сценарий. Я создаю виджет, который создает Midget из заданного текстового значения. Это всегда начинается очень просто, пока я не займусь деталями. Мой Виджет должен взаимодействовать с несколькими трудными для тестирования таких вещей, как файловые системы, базы данных и сеть.

Итак, вместо того, чтобы конструировать все это в свой виджет, я создаю сотрудника Bridget. Bridget заботится о половине сложности, базе данных и сети, позволяя мне сосредоточиться на другой половине, которая представляет собой мультимедийную презентацию. Итак, тогда я делаю Gidget, который выполняет мультимедийную пьесу. Все это должно происходить в фоновом режиме, поэтому теперь я включаю Thridget, чтобы это произошло. Когда все сказано и сделано, я получаю виджет, который работает с Thridget, который говорит о Бриджит, чтобы дать результат Gidget.

Поскольку я работаю в CocoaTouch и пытаюсь избежать ложных объектов, я использую шаблон self-shunt, где абстракции над соавторами становятся протоколами, которые мой тест принимает. С 3+ сотрудниками мои тестовые воздушные шары и становятся слишком сложными. Даже использование чего-то типа мошеннических объектов OCMock оставляет меня с рядом сложностей, которые я бы предпочел избежать. Я попробовал обернуть мозг вокруг цепочек последователей (делегаты в B, которые делегируют C и т.д.), Но я не могу этого себе представить.

Edit Взяв пример снизу, предположим, что у нас есть объект, который должен читать/записывать из сокетов и предоставлять возвращенные данные фильма.

//Assume myRequest is a String param...
InputStream   aIn  = aSocket.getInputStram();
OutputStream  aOut = aSocket.getOutputStram();
DataProcessor aProcessor = ...;

// This gets broken into a "Network" collaborator.
for(stuff in myRequest.charArray()) aOut.write(stuff);
Object Data = aIn.read(); // Simplified read

//This is our second collaborator
aProcessor.process(Data);

Теперь вышеуказанное, очевидно, имеет дело с сетевой задержкой, поэтому оно должно быть Threaded. Это вводит абстракцию потока, чтобы вывести нас из практики многопоточных модульных тестов. Теперь имеем

AsynchronousWorker myworker = getWorker(); //here our third collaborator
worker.doThisWork( new WorkRequest() {
//Assume myRequest is a String param...
DataProcessor aProcessor = ...;

// Use our "Network" collaborator.
NetworkHandler networkHandler = getNetworkHandler();
Object Data = networkHandler.retrieveData(); // Simplified read

//This is our multimedia collaborator
aProcessor.process(Data);
})

Простите меня за работу без тестов, но я собираюсь вывести мою дочь на улицу, и я спешу на примере. Идея здесь заключается в том, что я организую сотрудничество нескольких сотрудников из-за простого интерфейса, который будет привязан к событию click button. Таким образом, самый большой тест отражает задачу Sprint, которая говорит, что при нажатии кнопки "Воспроизвести фильм" при нажатии на нее будет воспроизводиться фильм. Edit Давайте обсудим.

4b9b3361

Ответ 1

Наличие множества макетных объектов показывает, что:

1) У вас слишком много зависимостей. Пересмотрите свой код и попытайтесь сломать его дальше. В частности, попробуйте отделить преобразование и обработку данных.

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

В Java-сокете вам будет предоставлен набор параметров InputStream и OutputStream, чтобы вы могли считывать данные и отправлять данные своему сверстнику. Таким образом, ваша программа выглядит следующим образом:

InputStream  aIn  = aSocket.getInputStram();
OutputStream aOut = aSocket.getOutputStram();

// Read data
Object Data = aIn.read(); // Simplified read
// Process
if (Data.equals('1')) {
   // Do something
   // Write data
   aOut.write('A');
} else {
   // Do something else 
   // Write another data
   aOut.write('B');
}

Если вы хотите протестировать этот метод, вы должны создать mock для In и Out, для поддержки которых могут потребоваться довольно сложные классы.

Но если вы внимательно посмотрите, прочитайте с aIn и напишите в aOut, можно отделить его от обработки. Таким образом, вы можете создать еще один класс, который будет принимать прочитанный входной и возвращаемый выходные объекты.

public class ProcessSocket {
    public Object process(Object readObject) {
        if (readObject.equals(...)) {
       // Do something
       // Write data
       return 'A';
    } else {
       // Do something else 
       // Write another data
       return 'B';
   }
}

и ваш предыдущий метод будет:

InputStream   aIn  = aSocket.getInputStram();
OutputStream  aOut = aSocket.getOutputStram();
ProcessSocket aProcessor = ...;

// Read data
Object Data = aIn.read(); // Simplified read
aProcessor.process(Data);

Таким образом, вы можете протестировать обработку, не требуя макета. вы можете проверить:


ProcessSocket aProcessor = ...;
assert(aProcessor.process('1').equals('A'));

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

2) Вы тестируете модульное тестирование на unit test, что должно быть проверено на интеграцию.

Некоторые тесты не предназначены для модульного тестирования (в том смысле, что он требует излишней работы и не может эффективно получить хороший индикатор). Примерами таких тестов являются те, которые включают concurrency и пользовательские интерфейсы. Они требуют различных способов тестирования, чем модульное тестирование.

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

ИЗМЕНИТЬ

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

Программные компоненты или подкомпоненты связаны друг с другом каким-то образом, как символы объединяются в слова, слова объединяются в предложения, предложения к параграфам, абзацам к подразделу, разделу, главам и т.д.

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

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

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

Если это так, ваше решение должно сбалансировать тест. Если часть зависит от многих, и для этого требуется сложный набор макетов (или простое усилие для его проверки). Может быть, вам не нужно его проверять. Например, если A использует B, C использует B и B настолько прост в тестировании. Так почему бы вам просто не проверить A + B как один и C + B как пыльник. В моем примере, если SocketProcessor так сложно тестировать, слишком сложно до такой степени, что вы будете тратить больше времени на тестирование и поддерживать тесты больше, чем разрабатывать его, тогда это не стоит, и я просто проверю все сразу.

Без просмотра вашего кода (и с тем, что я никогда не разрабатываю CocaoTouch) это будет трудно сказать. И я могу дать хороший комментарий здесь. Извините: D.

ИЗМЕНИТЬ 2 См. Ваш пример, довольно ясно, что вы имеете дело с проблемой интеграции. Предполагая, что вы уже тестировали игровой фильм и пользовательский интерфейс отдельно. Понятно, зачем вам так много штучных объектов. Если вы впервые используете такую ​​структуру интеграции (этот параллельный шаблон), тогда эти макетные объекты могут быть действительно необходимы, и вы ничего не можете с этим поделать. Это все, что я могу сказать: -p

Надеюсь, что это поможет.

Ответ 2

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


То, что я ищу, - это лучшие методы использования TDD.

Википедия описывает TDD как

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

Далее будет указано:

  • Добавить тест
  • Запустите все тесты и посмотрите, не сработает ли новый.
  • Введите код
  • Запустите автоматические тесты и убедитесь, что они успешны.
  • Код рефакторинга

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

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

Мой цикл выглядит примерно так:

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

    а. Конечный пользователь (я) вводит пользователя через пользовательский интерфейс, предназначенный для реализации новой функции.

    б. Конечный пользователь (я) проверяет соответствующий выход программы, чтобы проверить, правильно ли вывод для данного входа

  • Когда я выполняю тестирование на шаге 4, тестовая среда фиксирует ввод пользователя и вывод программы в файл данных; тестовая среда может повторить такой тест в будущем (воссоздать ввод пользователя и утверждать, соответствует ли соответствующий вывод тому же, что и ожидаемый вывод, полученный ранее). Таким образом, тестовые примеры, которые были запущены/созданы на шаге 4, добавлены в набор всех автоматизированных тестов.

Я думаю, что это дает мне преимущества TDD:

  • Тестирование связано с развитием: я тестирую сразу после кодирования, а не перед кодированием, но в любом случае новый код проверяется перед его проверкой; никогда не был непроверенный код.

  • У меня есть автоматические комплекты тестов для регрессионного тестирования

Я избегаю некоторых затрат/недостатков:

  • Написание тестов (вместо этого я создаю новые тесты с использованием пользовательского интерфейса, который выполняется быстрее и проще и ближе к исходным требованиям)

  • Создание mocks (требуется для модульного тестирования)

  • Редактирование тестов при реорганизации внутренней реализации (поскольку тесты зависят только от открытого API, а не от внутренних деталей реализации).

Ответ 3

Мое решение (а не CocoaTouch) заключается в том, чтобы продолжать издеваться над объектами, но в refactory mock, настроенном на общий метод тестирования. Это уменьшает сложность самого теста, сохраняя при этом макетную инфраструктуру для изоляции моего класса.

Ответ 4

Чтобы избавиться от чрезмерного насмешки, вы можете следовать тестовой пирамиде, которая предполагает наличие большого количества тестов на единицу + компонент и меньшее количество медленных и хрупкие системные тесты. Это сводится к нескольким простым правилам:

  • Запишите тесты на минимально возможном уровне, которые не требуют насмешек. Если вы можете написать unit test (например, разбор строки), напишите его. Но если вы хотите проверить, вызывается ли синтаксический анализ на верхнем уровне, это потребует инициализации большего количества материала.
  • Макет внешних систем. Ваша система должна быть самостоятельной самостоятельной частью. Опираясь на внешние приложения (которые будут иметь свои собственные ошибки), сложно усложнить тестирование. Написание mocks/stubs намного проще.
  • После этого у вас будет несколько тестов, проверяющих ваше приложение с реальной интеграцией.

С этим мышлением вы устраняете почти все насмешки.