Определение полезного юнит-теста

47

Я просматривал документы phpunit и наткнулся на следующую цитату:

Вы всегда можете написать больше тестов. Однако вы быстро обнаружите, что только часть тестов, которые вы можете себе представить, действительно полезны. Вам нужно написать тесты, которые не будут работать, даже если вы считаете, что они должны работать, или тесты, которые пройдут успешно, даже если вы считаете, что они должны провалиться. Другой способ думать об этом с точки зрения затрат / выгод. Вы хотите написать тесты, которые окупят вас информацией. - Эрих Гамма

Это заставило меня задуматься. Как вы определяете, что делает юнит-тест более полезным, чем другой, кроме того, что указано в этой цитате о соотношении цена / качество. Как вы решаете, для какой части вашего кода вы создаете модульные тесты? Я спрашиваю это, потому что другая из этих цитат также сказала:

Так что, если речь идет не о тестировании, о чем оно? Речь идет о том, чтобы выяснить, что вы пытаетесь сделать, прежде чем пытаться это сделать. Вы пишете спецификацию, которая фиксирует небольшой аспект поведения в краткой, однозначной и выполнимой форме. Это так просто. Значит ли это, что вы пишете тесты? Нет. Это означает, что вы пишете спецификации того, что должен делать ваш код. Это означает, что вы заранее определяете поведение своего кода. Но не намного раньше времени. На самом деле, лучше всего, прежде чем писать код, потому что у вас под рукой столько информации, сколько у вас будет до этого момента. Как и хорошо сделанный TDD, вы работаете с небольшими приращениями ... определяя один небольшой аспект поведения за раз, затем реализуя его. Когда вы понимаете, что все дело в определении поведения, а не в написании тестов, ваша точка зрения меняется. Внезапно идея иметь тестовый класс для каждого из ваших производственных классов смехотворно ограничивает. И мысль о тестировании каждого из ваших методов с помощью собственного метода тестирования (в соотношении 1: 1) будет смешной. - Дэйв Астелс

Важный раздел этого

* И мысль о тестировании каждого из ваших методов с помощью собственного метода тестирования (в соотношении 1: 1) будет смешной. *

Итак, если создание теста для каждого метода «смешно», как и когда вы выбрали то, для чего пишете тесты?

zcourts
источник
5
Это хороший вопрос, но я думаю, что это независимый от языка программирования вопрос - почему вы пометили его как php?
Вот несколько действительно хороших статей, которые дали мне хорошее представление о том, для каких модульных тестов я должен писать и для каких областей важны автоматические тесты. - blog.stevensanderson.com/2009/11/04/… - blog.stevensanderson.com/2009/08/24/… - ayende.com/blog/4218/scenario-driven-tests
Кейн,

Ответы:

27

Сколько тестов на метод?

Ну, теоретический и крайне непрактичный максимум - сложность N-Path (предположим, что все тесты охватывают код различными способами;)). Минимум ОДИН !. Для открытого метода, то есть он не тестирует детали реализации, только внешние поведения класса (возвращают значения и вызывают другие объекты).

Вы цитируете:

* И мысль о тестировании каждого из ваших методов с помощью собственного метода тестирования (в соотношении 1: 1) будет смешной. *

а затем спросите:

Итак, если создание теста для каждого метода «смешно», как и когда вы выбрали то, для чего пишете тесты?

Но я думаю, что вы неправильно поняли автора здесь:

Идея « one test methodпер» one method in the class to test- это то, что автор называет «смехотворным».

(По крайней мере, для меня) Это не о «меньше», а о «больше»

Итак, позвольте мне перефразировать, как я понял его:

И мысль о тестировании каждого из ваших методов ТОЛЬКО ОДНЫМ МЕТОДОМ (его собственный метод тестирования в соотношении 1: 1) будет смешной.

Чтобы процитировать вашу цитату снова:

Когда вы понимаете, что все дело в определении поведения, а не в написании тестов, ваша точка зрения меняется.


Когда вы практикуете TDD, вы не думаете :

У меня есть метод, calculateX($a, $b);и ему нужен тест, testCalculcateXкоторый проверяет ВСЕ о методе.

TDD говорит вам о том, что ваш код ДОЛЖЕН ДЕЛАТЬ :

Мне нужно вычислить большее из двух значений ( первый тестовый случай! ), Но если $ a меньше нуля, то это должно привести к ошибке ( второй тестовый случай! ), А если $ b меньше нуля, это должно произойти .... ( третий тестовый кейс! ) и тд.


Вы хотите проверить поведение, а не только отдельные методы без контекста.

Таким образом, вы получаете набор тестов, который является документацией для вашего кода и ДЕЙСТВИТЕЛЬНО объясняет, что он должен делать, возможно, даже почему :)


Как вы решаете, для какой части вашего кода вы создаете модульные тесты?

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

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

TDD - это способ гарантировать, что у вас есть тесты на ВСЕ, но пока вы ПИШИТЕ тесты, все в порядке. Обычно написание их в один и тот же день помогает, потому что вы не собираетесь делать это позже, не так ли? :)



Ответ на комментарии:

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

Есть три вещи, которые эти методы могут вызывать:

Публичные методы других классов

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

* Защищенные или частные методы на том же *

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

Вы хотите проверить поведение, а не реализацию, и если класс выполняет все, он работает в одном большом публичном методе или во многих меньших защищенных методах, которые вызываются, является реализацией . Вы хотите иметь возможность ИЗМЕНИТЬ эти защищенные методы БЕЗ касания ваших тестов. Потому что ваши тесты сломаются, если ваш код изменит поведение! Вот для чего нужны ваши тесты, чтобы сказать вам, когда вы что-то сломаете :)

Открытые методы в одном классе

Это случается не очень часто, не так ли? И если это так, как в следующем примере, есть несколько способов справиться с этим:

$stuff = new Stuff();
$stuff->setBla(12);
$stuff->setFoo(14);
$stuff->execute(); 

То, что сеттеры существуют и не являются частью сигнатуры метода execute, является другой темой;)

Что мы можем проверить здесь, так это то, что, если execute устанавливает значение, когда мы устанавливаем неправильные значения Это setBlaвыдает исключение, когда вы передаете строку, может быть протестировано отдельно, но если мы хотим проверить, что эти два допустимых значения (12 и 14) не работают ВМЕСТЕ (по какой-либо причине), чем это один тестовый пример.

Если вам нужен «хороший» набор тестов, вы можете, в php, возможно (!) Добавить @covers Stuff::executeаннотацию, чтобы убедиться, что вы генерируете только покрытие кода для этого метода, а другие просто настраиваемые вещи нужно тестировать отдельно (опять же, если ты хочешь это).

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


* Примечание: вопрос был перенесен из SO и изначально касался PHP / PHPUnit, поэтому пример кода и ссылки взяты из мира php, я думаю, что это также применимо к другим языкам, так как phpunit не так сильно отличается от других xUnit рамки тестирования.

edorian
источник
Очень подробный и информативный ... Вы сказали: «Вы хотите протестировать поведение, а не только отдельные методы без контекста». Конечно, приличное количество методов не может быть протестировано в конкретном контексте, потому что они либо зависят, либо зависят от других методов. следовательно, полезное условие теста будет контекстным, только если будут проверяться иждивенцы? Или я неправильно понимаю, что вы имеете в виду>
zcourts
@ robinsonc494 Я отредактирую пример, который, возможно, немного лучше объясняет, куда я иду с этим
edorian
спасибо за правки и пример, это, безусловно, помогает. Я думаю, что моя путаница (если можно так назвать) заключалась в том, что, хотя я читал о тестировании на «поведение», каким-то образом я просто изначально (возможно?) Думал о тестовых случаях, которые были сосредоточены на реализации.
zcourts
@ robinsonc494 Может, подумаешь об этом так: если ты ударишь кого-то, кого Эфир нанесет ответный удар, вызови полицию или убежит. Это поведение. Это то, что делает Человек. Тот факт, что он использует свои мидии, вызванные небольшими электрическими зарядами от его мозга, является реализацией. Если вы хотите проверить реакцию кого-то, вы ударите его и посмотрите, будет ли он вести себя так, как вы ожидаете. Вы не помещаете его в сканер мозга и не видите, посылают ли импульсы мидиям. Некоторые идут на уроки в значительной степени;)
edorian
3
Я думаю, что лучший пример TDD, который действительно помог ему понять, как писать тесты, - это игра Kata Bowling Game от Uncle Bob Martin. slideshare.net/lalitkale/bowling-game-kata-by-robert-c-martin
Эми Анушевски
2

Тестирование и модульное тестирование - это не одно и то же. Модульное тестирование является очень важной и интересной частью общего тестирования. Я бы сказал, что фокус модульного тестирования заставляет нас задуматься об этом виде тестирования способами, которые несколько противоречат приведенным выше цитатам.

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

Во-вторых, модульные тесты действительно вступают в свои права, когда мы проводим рефакторинг. Мы меняем нашу реализацию, но ожидаем, что ответы останутся прежними - модульное тестирование - это наша защита от нарушения нашего интерфейса интерфейса. Итак, еще раз мы ожидаем, что испытания пройдут.

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

Чтобы ответить на ваш явный вопрос: Unit Test для открытого интерфейса имеет значение.

отредактировано в ответном комментарии:

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

DJNA
источник
Просто чтобы убедиться, что я понимаю, что вы говорите: во время тестирования сосредоточьтесь на тестировании общедоступного интерфейса, верно? Предполагая, что это правильно ... разве нет большей вероятности оставить ошибки в закрытых методах / интерфейсах, для которых вы не выполняли модульные тесты, некоторые хитрые ошибки в непроверенном «частном» интерфейсе могут привести к прохождению теста, когда он должен был действительно потерпеть неудачу. Я ошибаюсь, думая так?
zcourts
Используя покрытие кода, вы можете сказать, когда код в ваших закрытых методах не выполняется во время тестирования ваших открытых методов. Если ваши открытые методы полностью покрыты, любые открытые закрытые методы явно не используются и могут быть удалены. Если нет, вам нужно больше тестов для ваших открытых методов.
Дэвид Харкнесс
2

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

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

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

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

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

Эдвард Брей
источник
+1 - я не обязательно согласен со всеми вашими пунктами (или пунктами статьи), но я согласен с тем, что большинство модульных тестов бесполезны, если они проводятся вслепую (подход TDD). Тем не менее, они чрезвычайно полезны, если вы умны решать, на что стоит потратить время, выполняя юнит-тесты. Я полностью согласен с тем, что вы получаете гораздо больше прибыли при написании тестов более высокого уровня, в частности, автоматизированного тестирования на уровне подсистем. Проблема с сквозными автоматизированными тестами состоит в том, что они будут сложными, если не совсем непрактичными, для системы, если она имеет какой-либо размер / сложность.
Данк
0

Я склонен придерживаться другого подхода к модульному тестированию, который, кажется, работает хорошо. Вместо того, чтобы думать о модульном тесте как о «тестировании некоторого поведения», я больше думаю о нем как о «спецификации, которой должен следовать мой код». Таким образом, вы можете в основном объявить, что объект должен вести себя определенным образом, и, учитывая, что вы предполагаете, что в другом месте вашей программы вы можете быть уверены, что он относительно свободен от ошибок.

Если вы пишете публичный API, это чрезвычайно ценно. Тем не менее, вам всегда понадобится хорошая доза комплексных интеграционных тестов, потому что приблизиться к 100% охвату модульных тестов, как правило, не стоит и пропустить то, что большинство сочло бы «непроверяемым» методами модульного тестирования (насмешка, и т.д)

Earlz
источник