Что понимается под «модулем» в модульном тестировании

9

Как я понимаю в теории под "модулем" люди подразумевают метод (в ООП). Но на практике тесты, которые проверяют какой-то метод изолированно, являются очень хрупкими тестами поведения (проверка не результата, а факта вызова некоторого метода зависимости). Так что я вижу много людей, которые под юнитом понимают небольшой набор тесно связанных классов. В этом случае только внешние зависимости моделируются / заглушки, а для зависимостей, которые находятся внутри блока, используются реальные реализации. В этом случае есть больше государственных, значимых (согласно спецификации) и не очень хрупких тестов. Таким образом, вопрос в том, как вы относитесь к этим подходам, и действительно ли это целесообразно называть модульным тестированием второго подхода или это может быть своего рода низкоуровневое интеграционное тестирование?

Если вы видите некоторые конкретные соображения по поводу применения TDD одним из этих способов тестирования, я был бы благодарен за ваши мысли.

SiberianGuy
источник

Ответы:

11

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

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

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

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

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

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

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

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

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

gbjbaanb
источник
10

Единица обычно определяется как « самая маленькая тестируемая часть приложения ». Да, чаще всего это означает метод. И да, это означает, что вам не следует проверять результат зависимого метода, а только то, что метод вызывается (и затем только один раз, если это возможно, не в каждом тесте для этого метода).

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

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

Именно эту проблему намеревается решить разработка , основанная на поведении , и в частности разработка , основанная на приемочных тестах . Но это не устраняет необходимость модульных тестов / разработки на основе тестов; это просто дополняет его.

прецизионный самописец
источник
Я действительно хотел сказать, что поведенческие тесты хрупки. Они часто становятся ложноотрицательными при изменении кодовой базы. С государственными тестами это случается реже (но государственные тесты очень редко бывают для юнит-тестирования)
SiberianGuy
@Idsa: Я немного потерян твоими определениями. Поведенческие тесты - это интеграционные тесты, тестирующие часть поведения, как указано. Читая ваш оригинальный вопрос, кажется, что когда вы говорите о государственных тестах, вы имеете в виду то же самое.
фунтовые
под состоянием я подразумеваю тест, который проверяет состояние, результат какой-либо функции; под поведением я подразумеваю тест, который проверяет не результат, а тот факт, что была вызвана некоторая функция
SiberianGuy
@Idsa: В этом случае я совершенно не согласен. То, что вы называете государственным тестом, я называю интеграцией. То, что вы называете поведением, я называю единицей. Интеграционные тесты по определению более хрупкие. Google "интеграционный тестовый модуль хрупкий", и вы увидите, что я не одинок.
фунтовые
Есть журнал статей о тестировании, но кто из них разделяет ваше мнение?
SiberianGuy
2

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

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

Грант Пэйлин
источник
1
не совсем, единица - это какой-то изолированный предмет, просто потому, что автоматизированный инструментарий предпочитает обрабатывать метод, так как это не делает его таким, и не делает его лучшим. «Изолированный» является ключом здесь. Даже если вы тестируете методы, вы также должны тестировать и частные.
gbjbaanb
1

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

Является ли это методом, классом или подсистемой, зависит от конкретного кода, общее правило не может быть дано.

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

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

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

JacquesB
источник