PHPUnit лучшие практики для организации тестов

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

Большинство вещей довольно ясны, только у меня есть некоторые проблемы с тем, как правильно организовать тестовые пакеты. Zend имеет AllTests.php, из которого загружаются другие тестовые наборы.
Жесткий взгляд на класс использует PHPUnit_Framework_TestSuite для создания объекта набора, а затем добавляет к нему другие пакеты, но если я смотрю в документах PHPUnit для организации тестов в версиях PHPUnit после 3.4, то есть только описание для XML или FileHierarchy, Тот, кто использовал классы для организации тестов, был удален.
Я не нашел ничего, что этот метод устарел, и проекты, подобные Zend, все еще используют его.

Но если он устарел, как я смогу организовать тесты в одной структуре с конфигурацией xml? Выполнение всех тестов не проблема, но как бы я организовал тесты (в xml), если бы я только хотел выполнить несколько тестов. Может быть, создание нескольких xmls, где я только укажу несколько тестов/тестовых наборов для запуска?

Итак, если бы я хотел только протестировать module1 и module2 приложения, у меня был бы дополнительный xml для каждого и определял тестовые пакеты только для этих модулей (классов, используемых модулем). А также тот, который определяет набор тестов для всех тестов?

Или было бы лучше использовать аннотацию @group для конкретных тестов, чтобы отметить их для модуля 1 или модуля2?

Заранее благодарим за то, что вы указали мне некоторые лучшие практики.

Ответ 1

Я начну с ссылки на руководство, а затем займусь тем, что я видел и слышал в поле.

Организация наборов тестов phpunit

Организация модулей/тестовых папок в файловой системе

Мой рекомендуемый подход сочетает файловую систему с конфигурацией xml.

tests/
 \ unit/
 | - module1
 | - module2
 - integration/
 - functional/

с a phpunit.xml с простым:

<testsuites>
  <testsuite name="My whole project">
    <directory>tests</directory>
  </testsuite>
</testsuites>

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

Запуск phpunit затем выполнит ВСЕ тесты, а запуск phpunit tests/unit/module1 будет запускать все тесты модуля1.

Организация папки "unit"

Наиболее распространенный подход заключается в том, чтобы отразить структуру каталогов source/ в структуре папок tests/unit/.

У вас есть один TestClass на ProductionClass, так что это хороший подход в моей книге.

В организации файлов

  • Один класс для каждого файла.

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

  • У вас нет тестового пространства имен

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

Выполнение только нескольких тестов

Например phpunit --filter Factory выполняет все FactoryTests, а phpunit tests/unit/logger/ выполняет все связанные с журналом записи.

Вы можете использовать теги @group для чего-то вроде номеров выпуска, рассказов или чего-то, но для "модулей" я бы использовал макет папки.

Несколько xml файлов

Может быть полезно создать несколько xml файлов, если вы хотите:

  • один без покрытия кода
  • один только для модульных тестов (но не для функциональных или интеграционных или длительных тестов)
  • другие распространенные случаи "фильтра"
  • PHPBB3, например, делает это для their phpunit.xmls

Покрытие кода для ваших тестов

Поскольку это связано с запуском нового проекта с тестами:

  • Мое предложение - использовать теги @covers как описано в моем блоге (Только для модульных тестов всегда охватывают все не публичные функции, всегда используют теги покрытий.
  • Не создавайте покрытие для ваших интеграционных тестов. Это дает вам ложное чувство безопасности.
  • Всегда используйте белый список, чтобы включить весь ваш производственный код, чтобы номера не лгали вам!

Автозагрузка и загрузка ваших тестов

Вам не нужна какая-либо автоматическая загрузка для ваших тестов. PHPUnit позаботится об этом.

Используйте атрибут <phpunit bootstrap="file">, чтобы указать свой тестовый бутстрап. tests/bootstrap.php - это отличное место для его размещения. Там вы можете настроить автозагрузчик приложений и т.д. (Или называть их загрузкой приложений).

Резюме

  • Используйте xml-конфигурацию для почти всего.
  • Индивидуальные тесты интеграции и интеграции
  • Ваши папки unit test должны отражать структуру папок ваших приложений.
  • Для выполнения только определенных тестов используйте phpunit --filter или phpunit tests/unit/module1
  • Используйте режим strict с самого начала и никогда не отключите его.

Примеры проектов для просмотра

Ответ 2

Базовая структура каталогов:

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

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

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

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

Один тестовый класс на класс:

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

ОБНОВЛЕНИЕ Я все еще использую и мне нравится этот подход, но я нашел очень хорошую технику для уменьшения количества тестового кода и количества дублирования. Важно написать повторно используемые методы утверждений внутри самого тестового класса, который интенсивно используется тестовыми методами в этом классе. Это помогает мне придумать правильные типы методов утверждений, если я считаю их внутренними DSL (то, что продвигает дядя Боб, на самом деле он поощряет создание внутренних DSL). Иногда вы можете продвинуть эту концепцию DSL еще дальше (фактически сделать DSL), приняв строковый параметр, который имеет простое значение, указывающее, какой тип теста вы пытаетесь выполнить. Например, однажды я создал метод многократного использования, который принимал параметр $ left, $ comparesAs и $ right. Это сделало тесты очень короткими и удобочитаемыми, поскольку код читал что-то вроде $this->assertCmp('a', '<', 'b').

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