Базовая структура каталогов :
Я экспериментировал с размещением тестового кода рядом с тестируемым кодом, буквально в том же каталоге с немного отличающимся именем файла от файла с кодом, который он тестирует. Пока мне нравится этот подход. Идея в том, что вам не нужно тратить время и энергию на синхронизацию структуры каталогов между вашим кодом и тестовым кодом. Поэтому, если вы измените имя каталога, в котором находится код, вам также не нужно будет искать и изменять имя каталога для тестового кода. Это также заставляет вас тратить меньше времени на поиск тестового кода, который идет с каким-то кодом, так как он прямо рядом с ним. Это даже облегчает создание файла с тестовым кодом для начала, потому что вам не нужно сначала находить каталог с тестами, возможно, создать новый каталог, соответствующий тому, для которого вы создаете тесты, и затем создайте тестовый файл. Вы просто создаете тестовый файл прямо здесь.
Одним из огромных преимуществ этого является то, что другие сотрудники (а не вы, потому что вы никогда этого не сделаете) с меньшей вероятностью будут избегать написания тестового кода для начала, потому что это слишком много работы. Даже если они добавляют методы к существующим классам, они с меньшей вероятностью не захотят добавлять тесты к существующему тестовому коду из-за низкого трения при поиске тестового кода.
Один недостаток заключается в том, что это затрудняет выпуск вашего рабочего кода без сопровождающих его тестов. Хотя, если вы используете строгие соглашения об именах, это все еще возможно. Например, я использовал ClassName.php, ClassNameUnitTest.php и ClassNameIntegrationTest.php. Когда я хочу запустить все модульные тесты, есть набор, который ищет файлы, заканчивающиеся в UnitTest.php. Набор интеграционных тестов работает аналогично. Если бы я захотел, я мог бы использовать подобную технику, чтобы предотвратить выпуск тестов в производство.
Другим недостатком этого подхода является то, что когда вы просто ищете реальный код, а не тестовый код, требуется немного больше усилий, чтобы провести различие между ними. Но я чувствую, что это на самом деле хорошо, так как заставляет нас чувствовать боль реальности того, что тестовый код тоже является кодом, он добавляет свои собственные затраты на обслуживание и является такой же жизненно важной частью кода, как и все остальное, а не просто что-то в стороне где-то.
Один тестовый класс на класс:
Это далеко не экспериментально для большинства программистов, но для меня. Я экспериментирую только с одним тестовым классом на тестируемый класс. В прошлом у меня был целый каталог для каждого тестируемого класса, а затем у меня было несколько классов внутри этого каталога. Каждый тестовый класс настраивал тестируемый класс определенным образом, а затем имел набор методов, каждый из которых сделал свое утверждение. Но затем я начал замечать, что некоторые условия, в которые я помещал эти объекты, имели нечто общее с другими условиями, в которые он попал из других тестовых классов. Дублирование стало слишком большим для обработки, поэтому я начал создавать абстракции для его удаления. Тестовый код стал очень сложным для понимания и сопровождения. Я понял это, но я не мог видеть альтернативу, которая имела бы смысл для меня. Кажется, что наличие одного тестового класса для каждого класса не позволяет протестировать почти достаточное количество ситуаций, не перегружая себя тем, что весь этот тестовый код находится внутри одного тестового класса. Теперь у меня другая точка зрения на это. Даже если я был прав, это огромное препятствие для других программистов и меня, желающих писать и поддерживать тесты. Сейчас я экспериментирую с тем, чтобы заставить один тестовый класс на каждый тестируемый класс. Если я сталкиваюсь со слишком многими вещами для тестирования в этом одном тестовом классе, я экспериментирую с тем, чтобы рассматривать это как указание на то, что тестируемый класс делает слишком много и должен быть разбит на несколько классов. Для устранения дублирования я стараюсь как можно больше придерживаться более простых абстракций, которые позволяют всему существовать в одном читаемом тестовом классе.
UPDATE
Я все еще использую и мне нравится этот подход, но я нашел очень хорошую технику для уменьшения количества тестового кода и количества дублирования. Важно написать повторно используемые методы утверждений внутри самого тестового класса, который интенсивно используется тестовыми методами в этом классе. Это помогает мне придумать правильные типы методов утверждений, если я считаю их внутренними DSL (то, что продвигает дядя Боб, на самом деле он поощряет создание внутренних DSL). Иногда вы можете продвинуть эту концепцию DSL еще дальше (фактически сделать DSL), приняв строковый параметр, который имеет простое значение, указывающее, какой тип теста вы пытаетесь выполнить. Например, однажды я создал метод многократного использования, который принимал параметр $ left, $ comparesAs и $ right. Это сделало тесты очень короткими и удобочитаемыми, поскольку код читал что-то вроде $this->assertCmp('a', '<', 'b')
.
Честно говоря, я не могу особо подчеркнуть этот момент, это вся основа того, чтобы написание тестов было чем-то устойчивым (что вы и другие программисты хотите продолжать делать). Это делает возможным то, что тесты добавляют больше, чем то, что они убирают. Дело не в том, что вам нужно использовать именно эту технику, а в том, что вам нужно использовать какие-то абстракции многократного использования, которые позволяют вам писать короткие и удобочитаемые тесты. Может показаться, что я не в теме от вопроса, но на самом деле это не так. Если вы этого не сделаете, вы в конечном итоге попадете в ловушку необходимости создания нескольких тестовых классов для каждого тестируемого класса, и от этого дела действительно пойдут на спад.