Что делает хороший модульный тест? говорит, что тест должен проверять только одно. Какая от этого польза?
Не лучше ли было бы написать тесты побольше, которые проверяют больший блок кода? В любом случае расследовать ошибку теста сложно, и я не вижу в этом помощи более мелких тестов.
Обновлено: слово «единица» не так важно. Допустим, я считаю, что устройство немного больше. Здесь проблема не в этом. Настоящий вопрос заключается в том, зачем делать один или несколько тестов для всех методов, поскольку несколько тестов, охватывающих многие методы, проще.
Пример: класс списка. Почему я должен делать отдельные тесты для добавления и удаления? Один тест, который сначала добавляет, а затем удаляет звуки проще.
Потому что, если он проверяет несколько вещей, это будет называться тестом изобилия.
Ответ на вопрос «Считаете ли вы, что модульные тесты - это бомба?» обычно сводится к вопросу «Насколько хорошо вы разбираетесь в макетах и архитектуре кода?». Если вы не можете разбить свой код на отдельные блоки для тестирования (имитируйте входы и выходы и запускайте только тестируемый код), то модульные тесты просто не подходят. Вы обнаружите, что снова и снова пишете одни и те же сетапы / разборки, и на их выполнение уйдет целая вечность.





Тестирование только одного элемента позволит выделить его и доказать, работает оно или нет. В этом заключается идея модульного тестирования. Нет ничего плохого в тестах, которые проверяют более одного объекта, но это обычно называется интеграционным тестированием. У них обоих есть достоинства, в зависимости от контекста.
Например, если ваша прикроватная лампа не включается, а вы заменяете лампочку и переключаете удлинитель, вы не знаете, какое изменение устранило проблему. Надо было провести модульное тестирование и разделить ваши проблемы, чтобы изолировать проблему.
Обновление: я прочитал эту статью и связанные статьи, и я должен сказать, я потрясен: https://techbeacon.com/app-dev-testing/no-1-unit-testing-best-practice-stop-doing-it
Здесь есть субстанция, и по ней текут ментальные соки. Но я считаю, что это согласуется с первоначальным мнением о том, что мы должны проводить тест, который требует контекст. Я полагаю, я бы просто добавил это, чтобы сказать, что нам нужно быть ближе к тому, чтобы точно знать преимущества различного тестирования в системе и меньше подхода скрещивания пальцев. Измерения / количественные оценки и все такое хорошее.
Почему важно знать все сразу? Я могу исправить ошибку, а затем снова запустить тест, чтобы получить следующий.
«Модульное» тестирование, по определению, проверяет модуль вашей программы (то есть по отдельности) за раз.
Безусловно, вы можете сделать это таким образом, если это сработает для вас. Мне нелегко подчиняться методологиям. Я просто делаю то, что работает в контексте.
@iny - Конечно, но если для выполнения тестового прогона требуется 30 минут, вам может потребоваться более подробный отчет о тестировании и одновременно исправить кучу
@Newtopian - Выполнить только неудавшийся тест довольно просто.
Когда вы пишете узконаправленные модульные тесты, вы можете быстрее находить и исправлять дефекты в коде. Также легче измерить свой прогресс; Если вы тестируете 50 функций, более целесообразно показать, что 30 тестов за 50 пройдены, чем показать этот 1 тест за 5 прохождений.
Как показывает практика, это звучит довольно разумно. Но на это утверждение, как и на большинство других, можно законно ответить, сказав: «Это зависит от обстоятельств».
Люблю свой значок / фото! Вы настоящий вундеркинд.
Я думаю, что буду педантичен и укажу, что единица - это совокупность или контейнер «вещей». Двигайтесь как единое целое, как единое целое и так далее.
Я согласен с этим ответом. Достаточно интересно, что если вы пишете небольшие тесты, которые проверяют только одну вещь, вы в конечном итоге тяготеете к дизайну, который легче тестировать отдельные вещи ... который, как правило, требует меньшего состояния настройки, что в конечном итоге намного быстрее, чем большие интеграционные тесты, и предоставляет гораздо больше больше уверенности. Я думаю, что реалистичной целью должно быть выполнение 80 000 тестов менее чем за 10 минут. То есть подавляющее большинство ваших тестов должно выполняться менее чем за 10 мс каждый с меньшим количеством (но важных) интеграционных тестов. Ни один не работает дольше 10 секунд. Проверка рациона временем.
Тесты, которые проверяют более чем одну вещь, обычно не рекомендуются, потому что они более тесно связаны и хрупки. Если вы измените что-то в коде, изменение теста займет больше времени, поскольку есть еще кое-что, что нужно учесть.
[Редактировать:] Хорошо, скажем, это образец метода тестирования:
[TestMethod]
public void TestSomething() {
// Test condition A
// Test condition B
// Test condition C
// Test condition D
}
Если ваш тест на условие A не прошел, то B, C и D также окажутся неудачными и не принесут вам никакой пользы. Что, если бы изменение вашего кода также привело бы к сбою C? Если бы вы разделили их на 4 отдельных теста, вы бы это знали.
Но написание небольших тестов также занимает больше времени, поскольку для его настройки нужно писать больше кода. Вы не можете удалить, не создав ничего. Почему бы не создать, а затем удалить в одном и том же тесте?
Я запуталась, что именно здесь «создано» и «удалено»? По моему опыту, когда у меня длинные монолитные тесты, я трачу больше времени на отладку их, чем на код, который они тестируют.
Тем не менее, это хорошее обсуждение, и мне нравится, что вы отстаиваете свое мнение, даже если я думаю, что вы ошибаетесь :)
Смотрите дополнение в вопросе.
Если вы тестируете более одного объекта, и первое, что вы тестируете, терпит неудачу, вы не узнаете, пройдут ли последующие объекты, которые вы тестируете, или нет. Это легче исправить, когда вы знаете все, что выйдет из строя.
Хааа ... юнит-тесты.
Если зайти слишком далеко с любыми «директивами», он быстро станет непригодным для использования.
Единичный модульный тест - это такая же хорошая практика, как и одиночный метод, выполняющий единственную задачу. Но IMHO, это не означает, что один тест может содержать только один оператор assert.
Является
@Test
public void checkNullInputFirstArgument(){...}
@Test
public void checkNullInputSecondArgument(){...}
@Test
public void checkOverInputFirstArgument(){...}
...
лучше чем
@Test
public void testLimitConditions(){...}
На мой взгляд, это вопрос вкуса, а не хорошей практики. Я лично предпочитаю последнее.
Но
@Test
public void doesWork(){...}
на самом деле "директива" хочет, чтобы вы избегали любой ценой и что быстрее всего истощает мое рассудок.
В качестве окончательного вывода сгруппируйте вместе вещи, которые семантически связаны и легко тестируются вместе, чтобы сообщение о неудачном тесте само по себе было достаточно значимым, чтобы вы могли перейти непосредственно к коду.
Эмпирическое правило отчета о неудавшемся тесте: если вам сначала нужно прочитать код теста, то ваш тест недостаточно хорошо структурирован и требует большего разделения на более мелкие тесты.
Мои 2 цента.
Если среда тестирования может определить место сбоя в тесте с помощью нескольких утверждений, это в значительной степени ослабит ограничения модульного тестирования. Что касается приведенных выше примеров, я действительно могу пойти по другому пути.
«Единичный модульный тест - это такая же хорошая практика, как и одиночный метод, выполняющий единственную задачу». Забавно, что ты так говоришь. Потому что вам нужно иметь очень чистые функции / код, чтобы сделать возможным хорошее тестирование.
Я собираюсь рискнуть и сказать, что совет «только проверяйте одно» на самом деле не так полезен, как его иногда воображают.
Иногда тесты требуют определенной настройки. Иногда для их настройки может потребоваться определенное количество время (в реальном мире). Часто вы можете протестировать два действия за один раз.
Плюс: настройка выполняется только один раз. Ваши тесты после первого действия докажут, что мир такой, каким вы его ожидали до второго действия. Меньше кода, быстрее тестовый запуск.
Против: если действие либо завершится неудачно, вы получите тот же результат: тот же тест не удастся. У вас будет меньше информации о том, в чем проблема, чем если бы вы выполняли только одно действие в каждом из двух тестов.
На самом деле, я считаю, что "мошенничество" здесь не является большой проблемой. Трассировка стека часто сужает вещи очень быстро, и я все равно исправлю код.
Немного другой «минус» здесь состоит в том, что он прерывает цикл «написать новый тест, пройти, рефакторинг». Я рассматриваю это как цикл идеальный, но тот, который не всегда отражает реальность. Иногда просто более прагматично добавить дополнительное действие и проверку (или, возможно, просто еще одну проверку к существующему действию) в текущем тесте, чем создавать новый.
Как всегда, Джон, вы можете оказаться в затруднительном положении, но вы говорите разумно с той ветки, которую выбрали в качестве своей опоры.
Я согласен с вашей точкой зрения: хотя лучшей практикой может быть тестирование только одной функции за тест, ваша среда может требовать тестирования нескольких функций.
Слова что-то значат: модульный тест должен тестировать одну единицу программы. Один метод, одна функция. Интеграционные и функциональные тесты (которые можно автоматизировать!) Проверяют блоки большего размера. Я также проголосовал против, так как у спрашивающего, казалось, уже был ответ, и проигнорировал ответ, набрав больше голосов.
@Terry: Теоретически это звучит прекрасно, но, на мой взгляд, на практике это не работает в 100% случаев. Если в случаях немного вы получите более простой и меньший код, протестировав два действия в одном тестовом примере, где преимущество практичный в том, что этого не происходит?
@Jon: На практике мне легче тестировать небольшие фрагменты, YMMV. Ничего не работает на 100%, поэтому выбирайте с умом. Я бы добавил это в качестве довода против того, чтобы не выполнять надлежащие модульные тесты: требования написания кода для модульного тестирования могут принести пользу дизайну программного обеспечения (абстракция, инкапсуляция, короткие методы и т. д.)
@Terry: Мне нравится тестировать небольшие фрагменты где я могу, но я бы предпочел тестировать большие фрагменты, чем не тестировать вообще, и я бы предпочел иметь большие фрагменты с кодом, в котором я уверен, чем без кода вообще, потому что я могу '' Я не хочу вдаваться в подробности в небольших тестах. Цельтесь высоко, но будьте практичны.
@Jon: Пример списка в Q. описывает множество утверждений в рамках одного метода тестирования. Для меня это все еще звучит как модульный тест, ничего плохого в повторном использовании настройки. Согласен, лучше большой тест, например, CPPUNIT_ASSERT (true == irs.paidTaxesLastFiveYears ()), а затем без теста. Семантика утверждения против теста?
Я бы не хотел слишком много говорить о семантике утверждения и теста. Я бы, конечно, предпочел тест с множеством утверждений, чем тест, который делал бы много разных вещей ... но иногда я нахожу, что последнее неизбежно :(
Одна проблема с тестированием большими порциями заключается в том, что тогда тесты предоставляют меньшую ценность для документации: короткое имя в большом тесте не может описать полное намерение автора, что, в свою очередь, требует от читателя реконструировать намерение автора из тестового кода. и реализация. См. Мой ответ и статью, на которую есть ссылка.
@Esko: Я не предлагаю тесты громадный - просто не «одно утверждение на тест». Можно найти золотую середину.
Хорошо, что вы не предлагаете огромных тестов, но показ некоторого кода прояснит, что вы имеете в виду - «маленький» для одного человека может быть «большим» для другого. «Одно утверждение на тест» можно интерпретировать по-разному. Жесткая буквальная интерпретация «один оператор утверждения на тест» не всегда практична, потому что отдельные утверждения утверждения имеют очень низкий уровень, и иногда вы не можете проверить значение только одним утверждением, но интерпретация более высокого уровня «одна концепция на тест» важнее. См. «Чистый код», страницы 130–132.
@Esko: Тип кода, в котором это действительно имеет значение, - это бизнес-код с сложной настройкой и т. д. Примеры, такие как коллекции, в конечном итоге предпочитают очень маленькие тесты по самой своей природе - только код настоящий действительно имеет значение, и это обычно конфиденциально. Я по-прежнему считаю, что есть много места для тестов различного размера - некоторые очень низкого уровня, а некоторые относительно высокого уровня (даже если они все еще не являются полными приемочными тестами).
Что ж .. это касается того же вопроса, что каждый тест должен иметь только одно утверждение. Я считаю, что проблема в том, что каждый тест должен быть атомарным. Если у вас более одного утверждения, у вас больше нет атомарного теста, поскольку вы проверяете что-либо только до первого неудачного утверждения.
@txwikinger: Возникает вопрос, почему вы считаете, что каждый тест должен быть атомарным. Я искренне верю, что тестирование должно быть прагматичным. Если использование более одного утверждения (для связанных вещей, конечно) в тесте делает этот тест более легким для чтения и более быстрым для написания, чем их разделение, я не вижу большого вреда. Обычно, если первое утверждение терпит неудачу, это указывает на проблему, которая, вероятно, в любом случае приведет к сбою второго утверждения. Не могу поверить, что при таком подходе я потерял значительное количество времени - скорее, я считаю, что сэкономил время и получил более понятные тесты.
@Jon Skeet: Ну, прагматизм означает знать, когда правила применяются, а когда нет, а не то, что у вас нет разумных правил. Во-вторых, я бы назвал вашу философию тестирования скорее интеграционным тестированием, чем модульным тестированием, которое тоже важно и должно быть выполнено в рамках тестирования. В идеале модульное тестирование должно иметь разделенные тесты, что не так, если вы предполагаете, что второе утверждение не сработает в любом случае, если первое.
@txwikinger: Нет, интеграционное тестирование для меня совсем другое. Это означает тестирование интеграции нескольких компонентов. Я вообще не об этом говорю. Я говорю об утверждении нескольких вещей либо о результатах одной операции, либо о выполнении одной операции логичный, которая может включать несколько вызовов и утверждений по пути.
@Jon Skeet: Может быть, это одно из тех исключений из правил. Однако, возможно, ваш код нуждается в рефакторинге из-за того, что слишком много вещей являются частью одной логической операции. Такие вопросы всегда сложно обсуждать чисто абстрактно. IMHO имеет смысл попытаться разработать модульные тесты, не требующие таких сложных сборок, поскольку это создает риск того, что такие тесты труднее поддерживать, когда проект повторяется и все меняется, в частности, рефакторинг. Как указывалось ранее, руководящие принципы подходят не для каждой ситуации, и различные аспекты качества часто противоречат друг другу.
Раньше меня это очень сильно обжигало. На что вы должны обратить внимание, это когда вы разделяете состояние настройки между тестами, потому что «медленно» воссоздавать его каждый раз (красный флаг), и один из тестов не очищает себя должным образом. Это легко исправить, и в итоге вы получите случайные неудачные тесты, поскольку, когда тесты проходят изолированно или в другом порядке, обнаружение ошибки может быть настоящим кошмаром. Однако тестировать только одну вещь не означает только одно утверждение. Используйте свое усмотрение и придерживайтесь ААА, если вы действуете после утверждения, то это красный флаг.
В pytest вы можете запустить установку только один раз для группы тестов. Я думаю, что это просто вопрос технологии, а не повод делать тесты некрасивыми, смешивая разную логику.
GLib, но, надеюсь, все еще полезный, ответ таков: unit = one. Если вы тестируете более одного объекта, значит, вы не тестируете модуль.
Используя разработку через тестирование, вы должны сначала написать свои тесты, а затем написать код для прохождения теста. Если ваши тесты сфокусированы, это упрощает написание кода для прохождения теста.
Например, у меня может быть метод, принимающий параметр. Одна из вещей, о которой я мог бы подумать в первую очередь, - что должно произойти, если параметр равен нулю? Он должен выбросить исключение ArgumentNull (я думаю). Поэтому я пишу тест, который проверяет, возникает ли это исключение, когда я передаю нулевой аргумент. Запустите тест. Хорошо, это выдает NotImplementedException. Я исправляю это, изменяя код, чтобы генерировать исключение ArgumentNull. Запустите мой тест, он прошел. Тогда я думаю, что будет, если он слишком маленький или слишком большой? Ах, это два теста. Сначала я напишу слишком маленький случай.
Дело в том, что я не думаю о поведении метода сразу. Я создаю его постепенно (и логически), думая о том, что он должен делать, а затем реализую код и рефакторинг по мере того, как я иду, чтобы он выглядел красиво (элегантно). Вот почему тесты должны быть небольшими и целенаправленными, потому что, когда вы думаете о поведении, вы должны развивать его небольшими, понятными шагами.
Это отличный ответ. Модульные тесты помогают при разработке, управляемой тестированием. Это отличный аргумент в пользу модульных тестов.
Я особо не думал об этом, но да. Тестирование только одной вещи (или мелочей) делает возможным TDD. Если бы ваши тесты были большими, TDD был бы ужасным способом написания программного обеспечения.
Меньшие по размеру модульные тесты проясняют, в чем проблема, когда они терпят неудачу.
Что касается вашего примера: если вы тестируете добавление и удаление в одном модульном тесте, как вы проверяете, был ли элемент когда-либо добавлен в ваш список? Вот почему вам нужно добавить и убедиться, что он был добавлен за один тест.
Или, используя пример лампы: если вы хотите проверить свою лампу и все, что вам нужно сделать, это включить, а затем выключить переключатель, как узнать, что лампа когда-либо включалась? Вы должны сделать промежуточный шаг, чтобы посмотреть на лампу и убедиться, что она горит. Затем вы можете выключить его и убедиться, что он выключился.
Между ними проще добавить assert.
Наличие тестов, которые проверяют только одно, упрощает поиск и устранение неисправностей. Это не значит, что у вас не должно быть тестов, которые тестируют несколько вещей, или нескольких тестов, которые используют одну и ту же настройку / разборку.
Вот наглядный пример. Допустим, у вас есть класс стека с запросами:
и методы для изменения стека
Теперь рассмотрим следующий тестовый пример (в этом примере я использую псевдокод Python).
class TestCase():
def setup():
self.stack = new Stack()
def test():
stack.push(1)
stack.push(2)
stack.pop()
assert stack.top() == 1, "top() isn't showing correct object"
assert stack.getSize() == 1, "getSize() call failed"
Из этого тестового примера вы можете определить, что что-то не так, но не изолировано ли это от реализаций push() или pop() или запросов, возвращающих значения: top() и getSize().
Если мы добавим отдельные тестовые примеры для каждого метода и его поведения, диагностировать станет намного проще. Кроме того, выполняя новую настройку для каждого тестового примера, мы можем гарантировать, что проблема полностью связана с методами, вызванными неудачным методом тестирования.
def test_size():
assert stack.getSize() == 0
assert stack.isEmpty()
def test_push():
self.stack.push(1)
assert stack.top() == 1, "top returns wrong object after push"
assert stack.getSize() == 1, "getSize wrong after push"
def test_pop():
stack.push(1)
stack.pop()
assert stack.getSize() == 0, "getSize wrong after push"
Что касается разработки через тестирование. Я лично пишу более крупные «функциональные тесты», которые заканчиваются сначала тестированием нескольких методов, а затем создают модульные тесты, когда я начинаю реализовывать отдельные части.
Другой способ взглянуть на это - это модульные тесты, проверяющие контракт каждого отдельного метода, в то время как более крупные тесты проверяют контракт, которому должны следовать объекты и система в целом.
Я все еще использую три вызова методов в test_push, однако и top(), и getSize() - это запросы, которые проверяются отдельными тестовыми методами.
Вы можете получить аналогичную функциональность, добавив больше утверждений к одному тесту, но тогда последующие сбои утверждений будут скрыты.
Во-первых, мне кажется, что вы тестируете три метода в test_push, а не один, и вам все равно нужно посмотреть, что assert не удалось выяснить, что не так. И эти два теста не проверяют такое поведение, как исходный комбинированный тест. Так почему бы не провести комбинированный тест с дополнительными утверждениями?
См. Сообщение для расширенного объяснения.
Я поддерживаю идею о том, что модульные тесты должны проверять только одно. Я также немного отклоняюсь от этого. Сегодня у меня был тест, в котором дорогая установка, казалось, заставляла меня делать более одного утверждения за тест.
namespace Tests.Integration
{
[TestFixture]
public class FeeMessageTest
{
[Test]
public void ShouldHaveCorrectValues
{
var fees = CallSlowRunningFeeService();
Assert.AreEqual(6.50m, fees.ConvenienceFee);
Assert.AreEqual(2.95m, fees.CreditCardFee);
Assert.AreEqual(59.95m, fees.ChangeFee);
}
}
}
В то же время мне очень хотелось увидеть все мои утверждения, которые потерпели неудачу, а не только первое. Я ожидал, что все они проиграют, и мне нужно было знать, какие суммы я действительно получаю. Но стандартный [SetUp] с каждым разделенным тестом вызовет 3 вызова медленной службы. Внезапно я вспомнил статью, в которой говорилось, что в использовании «нетрадиционных» тестовых конструкций скрыта половина преимуществ модульного тестирования. (Я думаю, что это был пост Джереми Миллера, но сейчас не могу его найти.) Внезапно [TestFixtureSetUp] всплыл в памяти, и я понял, что могу сделать один вызов службы, но все же иметь отдельные выразительные методы тестирования.
namespace Tests.Integration
{
[TestFixture]
public class FeeMessageTest
{
Fees fees;
[TestFixtureSetUp]
public void FetchFeesMessageFromService()
{
fees = CallSlowRunningFeeService();
}
[Test]
public void ShouldHaveCorrectConvenienceFee()
{
Assert.AreEqual(6.50m, fees.ConvenienceFee);
}
[Test]
public void ShouldHaveCorrectCreditCardFee()
{
Assert.AreEqual(2.95m, fees.CreditCardFee);
}
[Test]
public void ShouldHaveCorrectChangeFee()
{
Assert.AreEqual(59.95m, fees.ChangeFee);
}
}
}
В этом тесте больше кода, но он дает гораздо большую ценность, показывая мне сразу все значения, которые не соответствуют ожиданиям.
Коллега также отметил, что это немного похоже на specunit.net Скотта Беллвэра: http://code.google.com/p/specunit-net/
Другой практический недостаток очень детализированного модульного тестирования заключается в том, что оно нарушает СУХОЙ принцип. Я работал над проектами, в которых правилом было то, что каждый общедоступный метод класса должен иметь модульный тест ([TestMethod]). Очевидно, это добавляло некоторые накладные расходы каждый раз, когда вы создавали общедоступный метод, но реальная проблема заключалась в том, что это добавляло некоторое «трение» к рефакторингу.
Это похоже на документацию уровня метода, это приятно иметь, но это еще одна вещь, которую нужно поддерживать, и это делает изменение сигнатуры или имени метода немного более громоздким и замедляет «рефакторинг зубной нити» (как описано в «Инструменты рефакторинга: соответствие назначению» Эмерсона Мерфи-Хилла и Эндрю П. Блэк. PDF, 1,3 МБ).
Как и в большинстве случаев в дизайне, существует компромисс, который не отражает фраза «тест должен проверять только одно».
Подумайте о постройке машины. Если бы вы применили свою теорию о простом тестировании больших вещей, то почему бы не провести тест, чтобы проехать на машине по пустыне. Это ломается. Хорошо, скажи мне, в чем проблема. Вы не можете. Это тест сценария.
Функциональная проверка может заключаться в запуске двигателя. Это не удается. Но это могло быть по ряду причин. Вы все еще не могли сказать мне, что именно вызвало проблему. Но мы приближаемся.
Модульный тест является более конкретным и сначала определит, где код неисправен, но он также (при правильном TDD) поможет спроектировать ваш код в виде четких модульных фрагментов.
Кто-то упомянул об использовании трассировки стека. Забудь это. Это второе средство. Просматривать трассировку стека или использовать отладку сложно и может занять много времени. Особенно на больших системах и сложных ошибках.
Хорошие характеристики юнит-теста:
Когда тест не проходит, есть три варианта:
Детализированные тесты с описательные имена помогают читателю узнать Почему, что тест был написан, что, в свою очередь, упрощает определение того, какой из вышеперечисленных вариантов выбрать. Название теста должно описывать поведение, которое задается тестом - и только одно поведение за тест - так, чтобы, просто прочитав названия тестов, читатель знал, что делает система. См. эта статья для получения дополнительной информации.
С другой стороны, если один тест выполняет множество разных действий и имеет не описательное имя (например, тесты, названные в честь методов в реализации), то будет очень сложно выяснить мотивацию теста, и будет сложно понять, когда и как изменить тест.
Вот как это может выглядеть (с GoSpec), когда каждый тест проверяет только одно:
func StackSpec(c gospec.Context) {
stack := NewStack()
c.Specify("An empty stack", func() {
c.Specify("is empty", func() {
c.Then(stack).Should.Be(stack.Empty())
})
c.Specify("After a push, the stack is no longer empty", func() {
stack.Push("foo")
c.Then(stack).ShouldNot.Be(stack.Empty())
})
})
c.Specify("When objects have been pushed onto a stack", func() {
stack.Push("one")
stack.Push("two")
c.Specify("the object pushed last is popped first", func() {
x := stack.Pop()
c.Then(x).Should.Equal("two")
})
c.Specify("the object pushed first is popped last", func() {
stack.Pop()
x := stack.Pop()
c.Then(x).Should.Equal("one")
})
c.Specify("After popping all objects, the stack is empty", func() {
stack.Pop()
stack.Pop()
c.Then(stack).Should.Be(stack.Empty())
})
})
}
Разница здесь в том, что у вас есть вложенные тесты. Три теста: «нажатый последним выталкивается первым», «отправленный первым выталкивается последним» и «после этого стек пуст», по сути, являются подтестами. Это довольно изящный способ решения задач, но он не поддерживается (скажем) JUnit и NUnit. (Мне не очень нравится фраза «давайте сделаем все как по-английски», но это другой вопрос.) Как бы вы выразили эти тесты в JUnit? Как 5 отдельных тестов или 2? (Каждое из двух может содержать несколько утверждений - необязательно с сообщениями.)
В JUnit 4 я бы использовал простой пользовательский бегун, чтобы я мог использовать внутренние классы следующим образом: github.com/orfjackal/tdd-tetris-tutorial/blob/beyond/src/tes t /… В JUnit 3 он работает не так хорошо, но это возможно так: github.com/orfjackal/tdd-tetris-tutorial/blob/… В фреймворке, в котором нет приспособлений (например, as gotest), я бы неохотно записывал всю ту же информацию в имя одного метода. Отсутствие приспособлений приводит к большому дублированию.
Я не использовал ни NUnit, ни C#, но из nunit.org/index.php?p=quickStart&r=2.5.3 кажется, что NUnit изначально поддерживает этот стиль организации тестов. Просто поместите несколько тестовых устройств в одно и то же пространство имен, чтобы в одном файле / пространстве имен были все тестовые устройства, которые относятся к одному и тому же поведению.
Лучше всего, конечно, если среда тестирования уже поддерживает предпочтительный стиль написания тестов. В Java я в основном использовал JDave, в спецификациях Scala, в Ruby RSpec и т. д. И если ничего подходящего не существует, реализовать его самостоятельно можно за неделю. Так было с Go: единственным фреймворком был gotest, но он был слишком ограничен, gospecify находился в разработке, но у его автора были другие цели проекта (без изоляции побочных эффектов), поэтому я создал GoSpec 1.0 менее чем за 50 часов.
Если вы тестируете более одного объекта, это называется интеграционным тестом ... а не модульным тестом. Вы все равно будете запускать эти интеграционные тесты в той же среде тестирования, что и ваши модульные тесты.
Интеграционные тесты, как правило, медленнее, модульные тесты - быстро, потому что все зависимости имитируются / подделываются, поэтому нет вызовов баз данных / веб-сервисов / медленных сервисов.
Мы запускаем наши модульные тесты при фиксации в системе контроля версий, а наши интеграционные тесты запускаются только в ночной сборке.
The real question is why make a test or more for all methods as few tests that cover many methods is simpler.
Что ж, чтобы, когда какой-то тест не прошел, вы знали, какой метод не работает.
Когда вам нужно отремонтировать неисправный автомобиль, это проще, если вы знаете, какая часть двигателя выходит из строя.
An example: A list class. Why should I make separate tests for addition and removal? A one test that first adds then removes sounds simpler.
Предположим, что метод добавления не работает и не добавляет, а метод удаления не работает и не удаляет. Ваш тест проверит, что список после добавления и удаления имеет тот же размер, что и изначально. Ваш тест будет успешным. Хотя оба ваших метода сломались бы.
Отказ от ответственности: на этот ответ сильно повлияла книга «Тестовые шаблоны xUnit».
Тестирование только одного предмета в каждом тесте - один из самых основных принципов, который дает следующие преимущества:
Я вижу только одну причину, по которой вам может быть полезно иметь тест, который проверяет несколько вещей, но на самом деле это следует рассматривать как запах кода:
Тест, который проверяет более одного объекта, в большинстве случаев содержит меньше кода, который разделяет тесты. Совместное тестирование двух тесно связанных вещей гарантирует, что они действительно работают вместе.
Я думаю, что то, о чем вы говорите, немного выходит из контекста модульного тестирования и идет в сторону тестирования на уровне компонентов. При модульном тестировании вы в идеале хотите тестировать каждую часть функциональности полностью изолированной. При тестировании компонентов вам действительно может потребоваться протестировать 2 разных функциональных элемента вместе, если они обеспечивают больший набор функциональных возможностей для более высокого уровня иерархии дизайна.
Что ж, вы можете не поймать ошибку в своем коде, которая возникает только тогда, когда вы добавляете, а не удаляете.