TDD - Рефакторинг в черный ящик?

Мне разработали нетривиальный объект службы с 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.

6
задан Konrad Garus 30 June 2010 в 14:49
поделиться

3 ответа

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

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

6
ответ дан 10 December 2019 в 00:32
поделиться

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

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

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

[РЕДАКТИРОВАТЬ 1a] Это действительно упрощает тестирование с помощью насмешек. Метод go должен тестировать только с одним элементом очереди или без элементов очереди. Также каждый элемент теперь может иметь свои индивидуальные тесты отдельно от комбинаторного взрыва го .

[РЕДАКТИРОВАТЬ 1b] Возможно, вы даже сможете свернуть препроцесс в элемент:

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

Теперь у вас есть настоящий шаблон команды .

3
ответ дан 10 December 2019 в 00:32
поделиться

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

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

1
ответ дан 10 December 2019 в 00:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: