В обоснование test-drived design (TDD) постоянно приводят соображения, что без предварительного теста невозможно понять, рабочий ли тест вообще. Например, тут, дословно: "Сначала тест должен упасть. Это тест теста. Если тест не упал, я не могу быть уверен, что он упадет, когда надо."
В этом всём не додумывается один существенный момент: а если тестируемое изменилось (а особенно когда тест изменён вслед за этим - даже если просто переименован метод), как проверить, что тест действительно проверяет то, что нужно, а не просто эквивалентен exit(0)? Не писать же его заново. Писать отдельный тест для любого мельчайшего изменения, извините, не предлагать: в реальной жизни это просто недостижимо, тест должен проверять функциональность по спецификации, а не факт перемещения двух строк для лучшей читаемости.
Если так подумать, ответ становится тривиальным: зная структуру теста, мы должны иметь возможность внести в ключевых местах механизмы заведомой поломки тестируемой обстановки. Тогда проверка теста (мнэээ, это уже тестирование теста какое-то;)) состоит в проверке фактов, что
* тест без указания на модификацию обстановки - работает
* с каждым из таких указаний - ломается, причём характерным для этого образом (желательно видеть отражение в финальном коде завершения или сообщении об ошибке)
Например, если я проверяю в тесте приход определённого сообщения на шину, то очевидным вариантом сломать тест является не подписаться на шину или подписаться не на тот тип сообщения. Если в настройках должно стоять, какой выходной тег, то вариант поломки - заменить генерируемый тег. Если я проверяю, что выключение объекта привело к нотификации "ой, он выключился", то мы "забываем" выключить эмулятор объекта. И так далее. Для Unix я проверяю это установкой TEST_OPTIONS в окружении перед основным make теста, для других сред потребуется выбрать что-то соответствующее по вкусу.
С таким контролем становится неважно, написан тест до или после - более того, обеспечивая надлежащую расстановку контрольных точек мы обеспечиваем управляемость независимо от развития кода (см. выше про недостижимые требования).
Как читающие это уже поняли, я против TDD как тотального подхода, потому что TDD как подход - фактически обман, когда административные тезисы выдаются за технические. Требование "покажите, что ваш тест действительно работает" используется для маскировки требования не похерить само построение теста по обоснованиям недостатка времени, отсутствия необходимости проверять и тому подобному, а также для того, чтобы менеджмент получил формальные признаки готовности кода. Но так как всё покрыть тестами нельзя в принципе, (а 5% тестов покрывают 95% ошибок), то TDD изначально формально и практически некорректен. А вот behavior-driven development (тут или тут) при правильном подходе уже этим не страдает, но не по внутренним причинам, а потому, что определение "поведения" ограничивает тем, что действительно важно (а не несущественные особенности реализации).
В этом всём не додумывается один существенный момент: а если тестируемое изменилось (а особенно когда тест изменён вслед за этим - даже если просто переименован метод), как проверить, что тест действительно проверяет то, что нужно, а не просто эквивалентен exit(0)? Не писать же его заново. Писать отдельный тест для любого мельчайшего изменения, извините, не предлагать: в реальной жизни это просто недостижимо, тест должен проверять функциональность по спецификации, а не факт перемещения двух строк для лучшей читаемости.
Если так подумать, ответ становится тривиальным: зная структуру теста, мы должны иметь возможность внести в ключевых местах механизмы заведомой поломки тестируемой обстановки. Тогда проверка теста (мнэээ, это уже тестирование теста какое-то;)) состоит в проверке фактов, что
* тест без указания на модификацию обстановки - работает
* с каждым из таких указаний - ломается, причём характерным для этого образом (желательно видеть отражение в финальном коде завершения или сообщении об ошибке)
Например, если я проверяю в тесте приход определённого сообщения на шину, то очевидным вариантом сломать тест является не подписаться на шину или подписаться не на тот тип сообщения. Если в настройках должно стоять, какой выходной тег, то вариант поломки - заменить генерируемый тег. Если я проверяю, что выключение объекта привело к нотификации "ой, он выключился", то мы "забываем" выключить эмулятор объекта. И так далее. Для Unix я проверяю это установкой TEST_OPTIONS в окружении перед основным make теста, для других сред потребуется выбрать что-то соответствующее по вкусу.
С таким контролем становится неважно, написан тест до или после - более того, обеспечивая надлежащую расстановку контрольных точек мы обеспечиваем управляемость независимо от развития кода (см. выше про недостижимые требования).
Как читающие это уже поняли, я против TDD как тотального подхода, потому что TDD как подход - фактически обман, когда административные тезисы выдаются за технические. Требование "покажите, что ваш тест действительно работает" используется для маскировки требования не похерить само построение теста по обоснованиям недостатка времени, отсутствия необходимости проверять и тому подобному, а также для того, чтобы менеджмент получил формальные признаки готовности кода. Но так как всё покрыть тестами нельзя в принципе, (а 5% тестов покрывают 95% ошибок), то TDD изначально формально и практически некорректен. А вот behavior-driven development (тут или тут) при правильном подходе уже этим не страдает, но не по внутренним причинам, а потому, что определение "поведения" ограничивает тем, что действительно важно (а не несущественные особенности реализации).
no subject
Date: 2011-02-04 07:57 am (UTC)