TDD - Рефакторинг в черный ящик? - PullRequest
6 голосов
/ 30 июня 2010

У меня есть нетривиальный объект обслуживания, разработанный с помощью TDD.Это началось с простой задачи: для объекта из очереди создайте попытку асинхронной обработки.Итак, я написал тест для моего constructAttempt() метода:

void constructAttempt() {...}

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


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

public void go() {
    for (QueuedItem item : getQueuedItems()) {
        constructAttempt(item);
    }
}

Итак, я добавил новый или два теста для этого go() метода.


Наконец я обнаружил, что мне нужна предварительная обработка, которая иногда можетвлияет constructAttempt().Теперь код выглядит примерно так:

public void go() {
    preprocess();
    for (QueuedItem item : getQueuedItems()) {
        constructAttempt(item);
    }
}

У меня есть несколько сомнений относительно того, что я должен делать сейчас.

Должен ли я оставить код как есть, с constructAttempt(),preprocess() и go() проверены независимо?Почему да / почему нет?Я рискую не покрывать побочные эффекты предварительной обработки и прерывания инкапсуляции.

Или я должен реорганизовать весь свой набор тестов, чтобы вызвать только go() (который является единственным открытым методом)?Почему да / почему нет?Это сделало бы тесты немного более неясными, но с другой стороны, было бы учтено все возможные взаимодействия.Фактически, это стало бы тестом «черного ящика» с использованием только открытого API, что может не соответствовать TDD.

Ответы [ 3 ]

6 голосов
/ 30 июня 2010

Метод go на самом деле просто организует несколько взаимодействий и сам по себе не очень интересен.Если вы напишите свои тесты против go вместо подчиненных методов, тесты, вероятно, будут ужасно сложными, потому что вам придется учитывать комбинаторный взрыв взаимодействий между preprocess и constructAttempt (и, возможно, даже getQueuedItems(хотя это звучит относительно просто).

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

3 голосов
/ 30 июня 2010

@ Джефф прав.Что у вас есть, так это две обязанности, возникающие в этом объекте.Вы можете тянуть поставленные в очередь предметы в их собственный класс.Нажмите preprocess и constructAttempt на отдельные предметы.ИМХО, когда у вас есть класс, который имеет дело с отдельными предметами и список предметов, у вас есть запах кода.Обязанности заключаются в том, что контейнер воздействует на элементы.

public void go() {
    for (QueuedItem item : getQueuedItems()) {
        item.preprocess();
        item.constructAttempt();
    }
}

Примечание. Это похоже на работу с шаблоном объекта команды

[EDIT 1a] Это позволяет легко тестировать с помощью насмешек.Метод go необходимо тестировать только с одним элементом очереди или без элементов очереди.Также каждый item теперь может иметь свои индивидуальные тесты отдельно от комбинаторного взрыва go.

[РЕДАКТИРОВАТЬ 1b] Вы можете даже сложить preprocess впредмет:

public void go() {
    for (QueuedItem asyncCommunication: getQueuedItems()) {
        asyncCommunication.attempt();
    }
}

Теперь у вас есть истинный command pattern.

1 голос
/ 30 июня 2010

Я говорю, пусть ваш набор тестов только вызывает go (), так как это единственный публичный API. Это означает, что после того, как вы рассмотрели все сценарии для метода go (который включает в себя предварительную обработку и очередь), больше не имеет значения, если вы измените внутреннюю реализацию. Ваш класс остается верным, если речь идет о публичном использовании.

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

...