Каковы преимущества самотестирования кода перед отдельными тестами? - PullRequest
5 голосов
/ 04 февраля 2009

Лично я всегда помещал модульные тесты в отдельный проект только потому, что именно так настроен MSTest. Но я читаю Рефакторинг: Улучшение дизайна существующего кода Мартина Фаулера, и он, похоже, выступает за то, чтобы не только поместить их в один и тот же проект, но и поместить их в тот же класс, что и метод, которым они повторное тестирование.

Мне, честно говоря, трудно думать о том, как это отличается от того, чтобы тесты располагались отдельно от самого кода, кроме философских различий (документация по тестам или беспорядок?).

Есть ли какие-то четкие причины выбирать одно из другого? Или это в основном философская разница?

ОБНОВЛЕНИЕ : Я не обязательно убежден, так или иначе, но по крайней мере у меня есть идея, каковы аргументы. Хотелось бы, чтобы я мог выбрать ответ каждого, но мне нужно было выбрать только один.

Ответы [ 7 ]

5 голосов
/ 04 февраля 2009

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

  • Что делает класс (например, определение класса)
  • Как это делает (реализация)
  • Как вы его используете (документация и / или контрольные примеры)

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

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

4 голосов
/ 04 февраля 2009

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

Помещение тестов в другой файл, но тот же проект лучше, но все же заставляет ваш основной проект ссылаться на каркасы тестирования, такие как NUnit.Framework.dll, а также на любые насмешливые каркасы, такие как Rhino.Mocks.dll.

Наличие ваших тестов внутри тестируемого класса также увеличивает размер вашего распределенного проекта.

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

3 голосов
/ 04 февраля 2009

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

2 голосов
/ 04 февраля 2009

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

  • Невозможно развернуть и распространить производственный код без тестового кода. Поэтому вместо файла размером 100 КБ может потребоваться отправить файл размером 200 КБ или более. (При использовании TDD строки тестового кода часто равны или превышают строки производственного кода.)

  • Структура тестов слишком тесно связана с рабочим кодом. Между тестами и производственными классами не должно быть отношения 1: 1. Вместо этого между тестами и поведением должно быть отношение 1: 1.

Цитируется по http://blog.daveastels.com.s3.amazonaws.com/files/BDD_Intro.pdf

"Когда вы понимаете, что все дело в определении поведения, а не в написании тестов, ваша точка зрения меняется. Внезапно идея наличия класса Test для каждого из ваших производственных классов становится смехотворно ограничивающей. И мысль о тестировании каждого из ваших методы с собственным методом тестирования (в отношении 1-1) будут смешными. "

Я программирую в основном на Java и использую Maven для создания своих проектов. Там у меня есть тесты в том же модуле и пакете, что и рабочие классы, с которыми они работают, но в другом каталоге (/ src / main / java и / src / test / java). Когда Maven создает проект, он выполняет все тесты, но в двоичный дистрибутив включается только производственный код.


Приложение: 10 лет спустя (2019)

В настоящее время я в основном работаю в Clojure, и иногда мне бывает удобно писать тесты для функции прямо рядом с ней в том же файле. Это относится только к чистым модульным тестам для маленьких вспомогательных методов. (При статической типизации некоторые из этих тестов могут оказаться ненужными.) Clojure позволяет исключить тесты из скомпилированной производственной сборки, что позволяет избежать двоичного раздувания. А поскольку clojure.test является частью стандартной библиотеки, написание простых тестов в рабочих файлах не требует дополнительных зависимостей.

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

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

2 голосов
/ 04 февраля 2009

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

Сказав это, смысл TDD заключается не только в тестировании / обеспечении качества. Это больше о том, чтобы заставить вас задуматься о вашем дизайне / интерфейсах. И с этой точки зрения имеет смысл поместить ваш «тестовый» (a.k.a «дизайн») код в отдельный файл. Ваш «тестовый» код - клиент / пользователь вашего класса.

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

1 голос
/ 04 февраля 2009

Я думаю, это в основном философское различие.

Могут иметься незначительные преимущества в производительности, если использовать их в отдельном проекте (поскольку ваши тесты не развернуты в рабочей среде), но они, вероятно, не , что значимо.

Также помните, что Рефакторинг был написан довольно давно (в терминах ИТ), поэтому с тех пор предпочтительная практика вполне могла бы продолжиться.

0 голосов
/ 30 марта 2019

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

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

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

Программистам труднее быть ленивыми. Если тест «полностью», то программист должен найти тестовый код, прочитать его, найти соответствующую часть кода, который они только что рассматривали / думали об изменении. Но если у вас есть что-то более похожее на метод, который тестирует метод, а затем метод, который он тестирует, вам нужно быть очень ленивым, чтобы не «испытывать желание» найти тест.

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

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

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

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

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

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

...