Что вы должны проверить с юнит-тестами?


122

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

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

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


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

9
Интересно, что вы думаете об этом после почти 5 лет? Потому что все больше и больше я чувствую, что в наши дни люди должны лучше знать, «что не нужно тестировать юнитом». Развитие, основанное на поведении, развилось из вопросов, которые вы задали.
Ремигиюс Панкявичюс

Ответы:


121

Моя личная философия, таким образом, была:

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

1
Спасибо за это, я барахтался здесь с теми же вопросами, что и ОП.
Стивен

5
+1, хотя я бы также протестировал крайние варианты любых функций библиотечного / служебного типа, чтобы убедиться, что у вас есть логический API. например, что происходит, когда передается нулевое значение? как насчет пустого ввода? Это поможет убедиться, что ваш дизайн логичен и документирует поведение в угловых случаях.
Микера

7
№ 3 кажется очень убедительным ответом, поскольку это реальный пример того, как модульный тест мог бы помочь. Если это сломалось однажды, это может сломаться снова.
Райан Гриффит

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

Ваш ответ был показан в этой популярной средней статье: hackernoon.com/…
BugHunterUK

67

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

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

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

| # |  Equivalence class    | Input        | Output       | # test cases |
+------------------------------------------------------------------------+
| 1 | Lowercase letter      | a - z        | A - Z        | 26           |
| 2 | Uppercase letter      | A - Z        | A - Z        | 26           |
| 3 | Non-alphabetic chars  | 0-9!@#,/"... | 0-9!@#,/"... | 42           |
| 4 | Non-printable chars   | ^C,^S,TAB... | ^C,^S,TAB... | 34           |

В последнем столбце указано количество тестов, если вы перечислите все из них. Технически, по правилу 1 @ fishtoaster вы бы включили 52 тестовых случая - все из них для первых двух строк, приведенных выше, подпадают под «общий случай». Правило 2 @ fishtoaster также добавит некоторые или все строки 3 и 4 выше. Но при тестировании с разделением эквивалентности достаточно одного теста в каждом классе эквивалентности. Если вы выберете «a», «g» или «w», вы тестируете тот же путь кода. Таким образом, у вас есть 4 тестовых случая вместо 52+.

Анализ граничных значений рекомендует небольшое уточнение: по сути, он предполагает, что не каждый член класса эквивалентности, ну, в общем, эквивалентен. То есть значения на границах также должны рассматриваться как достойные тестового примера сами по себе. (Одно простое оправдание этому - печально известная ошибка «один на один» !) Таким образом, для каждого класса эквивалентности вы можете иметь 3 входных сигнала теста. Глядя на входную область выше - и с некоторыми знаниями о значениях ASCII - я мог бы придумать эти входные данные тестового примера:

| # | Input                | # test cases |
| 1 | a, w, z              | 3            |
| 2 | A, E, Z              | 3            |
| 3 | 0, 5, 9, !, @, *, ~  | 7            |
| 4 | nul, esc, space, del | 4            |

(Как только вы получите более 3 граничных значений, которые предполагают, что вы, возможно, захотите переосмыслить свои исходные разграничения классов эквивалентности, но это было достаточно просто, и я не стал возвращаться к их пересмотру.) Таким образом, анализ граничных значений подводит нас к 17 тестовых случаев - с высокой степенью достоверности полного охвата - по сравнению со 128 тестовыми случаями для проведения исчерпывающего тестирования. (Не говоря уже о том, что комбинаторика диктует, что исчерпывающее тестирование просто невозможно для любого реального приложения!)


3
+1 Именно так я интуитивно пишу свои тесты. Теперь я могу поставить имя :) Спасибо, что поделились этим.
guillaume31

+1 за «качественные ответы полезны, но возможно - и предпочтительнее - быть количественными»
Джимми Брек-МакКей

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

18

Наверное, мое мнение не слишком популярно. Но я предлагаю вам быть экономным с юнит-тестами. Если у вас слишком много модульных тестов, вы легко можете потратить половину своего времени или больше на поддержание тестов, а не на фактическое кодирование.

Я предлагаю вам написать тесты на вещи, которые у вас плохое предчувствие, или вещи, которые очень важны и / или элементарны. Модульные тесты IMHO не являются заменой для хорошего инженерного и защитного кодирования. В настоящее время я работаю над проектом, который более или менее непригоден. Это действительно стабильно, но боль от рефакторинга. Фактически никто не трогал этот код за один год, а программному стеку, на котором он основан, 4 года. Почему? Потому что он перегружен юнит-тестами, а точнее: юнит-тестами и автоматизированными интеграционными тестами. (Вы когда-нибудь слышали о огурце и тому подобном?) И вот лучшая часть: этот (пока) неиспользуемый кусок программного обеспечения был разработан компанией, сотрудники которой являются первопроходцами на сцене разработки, управляемой тестами. : D

Итак, мое предложение:

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

  • Убедитесь, что ваши юнит-тесты могут быть выполнены быстро. Если у вас есть интеграционные тесты (например, огурец), то все в порядке, если они занимают немного больше времени. Но длительные тесты не доставляют удовольствия, поверьте мне. (Люди забывают все причины, почему C ++ стал менее популярным ...)

  • Оставьте этот материал TDD экспертам TDD.

  • И да, иногда вы концентрируетесь на крайних случаях, иногда на общих случаях, в зависимости от того, где вы ожидаете неожиданного. Хотя, если вы всегда ожидаете неожиданного, вам следует по-настоящему пересмотреть свой рабочий процесс и дисциплину. ;-)


2
Можете ли вы дать более подробную информацию о том, почему тесты затрудняют рефакторинг этого программного обеспечения?
Майк Партридж

6
Большой +1. Наличие стенок модульных тестов, которые тестируют реализацию вместо правил, приводит к тому, что любое изменение требует в 2-3 раза больше
TheLQ

9
Как плохо написанный рабочий код, плохо написанные модульные тесты трудно поддерживать. «Слишком много юнит-тестов» звучит как неспособность оставаться СУХИМЫМ; каждый тест должен решать / доказывать определенную часть системы.
Аллан

1
Каждый модульный тест должен проверять одну вещь, поэтому существует не слишком много модульных тестов, но пропущенных тестов. Если ваши юнит-тесты сложные, это другая проблема.
График

1
-1: я думаю этот пост плохо написан. Упоминается несколько вещей, и я не знаю, как они все связаны. Если смысл ответа «быть экономным», то как ваш пример связан вообще? Похоже, в вашем примере ситуации (хотя и реальной) есть плохие юнит-тесты. Пожалуйста, объясните, какие уроки я должен извлечь из этого и как это помогает мне быть экономным. Кроме того, я, честно говоря, просто не знаю, что вы имеете в виду, когда говорите Leave this TDD stuff to the TDD-experts.
Александр Берд

8

Если вы сначала тестируете с помощью Test Driven Development, то ваш охват увеличится до 90% или выше, потому что вы не добавите функциональность, не написав сначала провальный модульный тест для нее.

Если вы добавляете тесты после факта, то я не могу порекомендовать вам достаточно, чтобы вы получили копию Майкла Фезерса « Эффективная работа с унаследованным кодом » и ознакомились с некоторыми методами добавления тестов в код и способами рефакторинга кода. чтобы сделать его более тестируемым.


Как вы рассчитываете этот процент покрытия? Что значит покрыть 90% вашего кода в любом случае?
zneak

2
@zneak: есть инструменты покрытия кода, которые рассчитают их для вас. Быстрый Google для «покрытия кода» должен вызвать ряд из них. Инструмент отслеживает строки кода, которые выполняются во время выполнения тестов, и базируется на том, что агентирует общее количество строк кода в сборке (ях), чтобы получить процент покрытия.
Стивен Эверс

-1. Не отвечает на вопрос:The problem is, I don't know _what_ to test
Александр Берд

6

Если начать следующий Test Driven развития практики, они будут своего рода руководство вас через процесс и зная , что тестирование будет, естественно. Некоторые места для начала:

Тесты на первом месте

Никогда, никогда не пишите код перед написанием тестов. Смотрите Red-Green-Refactor-Repeat для объяснения.

Написать регрессионные тесты

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

Красно-зеленый-Refactor-Repeat

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

Зеленый : написать самый простой и глупый код, который на самом деле проходит тест. Не пытайся быть умным. Даже если вы видите, что есть очевидный крайний случай, но тест учитывает, не пишите код для его обработки (но не забывайте о крайнем случае: он понадобится вам позже). Идея состоит в том, что каждый кусок кода, который вы пишете, каждый if, каждый, try: ... except: ...должен быть подтвержден тестовым примером. Код не должен быть элегантным, быстрым или оптимизированным. Вы просто хотите пройти тест.

Рефакторинг : Очистите ваш код, получите правильные имена методов. Посмотрите, проходит ли тест еще раз. Оптимизация. Запустите тест снова.

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

Проверьте свой код

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


1
Предполагая, что у меня уже есть существующая и (насколько я вижу) рабочая база кода, что мне делать?
2010 г.

Это может быть немного сложнее (в зависимости от того, как написан код). Начните с регрессионных тестов (они всегда имеют смысл), затем вы можете попробовать написать модульные тесты, чтобы доказать себе, что вы понимаете, что делает код. Легко быть пораженным количеством работы, которая (казалось бы) должна быть сделана, но: некоторые тесты всегда лучше, чем вообще никаких тестов.
Рышард Сзопа

3
-1 Не думаю, что это очень хороший ответ на этот вопрос . Вопрос не в TDD, а в том, что тестировать при написании модульных тестов. Я думаю, что хороший ответ на актуальный вопрос должен относиться к методологии без TDD.
Брайан Оукли

1
Если вы прикоснулись к нему, проверьте это. И «Чистый код» (Роберт С. Мартин) предлагает вам написать «учебные тесты» для стороннего кода. Таким образом, вы научитесь использовать его, и у вас есть тесты на случай, если новая версия изменит поведение, которое вы используете.
Роджер Уиллкокс

3

Для модульных тестов начните с тестирования того, что он делает то, для чего он предназначен. Это должен быть самый первый случай, когда ты пишешь. Если частью дизайна является «оно должно вызвать исключение, если вы проходите в мусор», проверьте это тоже, так как это является частью дизайна.

Начни с этого. По мере того, как вы приобретете опыт выполнения этого базового тестирования, вы начнете узнавать, достаточно ли этого или нет, и начнете видеть другие аспекты вашего кода, требующие тестирования.


0

Основной ответ - «проверить все, что может сломаться» .

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

Конечно, ваш пробег - и ваши рабочие условия - могут отличаться.


Хорошо. Итак, какие случаи я должен проверить? «Нормальный» случай? Крайний случай?
2010 г.

3
Практическое правило? Один или два прямо по середине золотого пути, и только внутри и снаружи любых краев.
Джеффри Хантин

@JeffreyHantin Это «анализ граничных значений» в другом ответе.
Роджер Уиллкокс
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.