Про юнит-тестирование (его также называют модульным) уже очень много сказано. Вот его определение из википедии:

Модульное тестирование, или юнит-тестирование (англ. unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы.

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

Немного теории

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

one does not simply write a unit tests

Чтобы писать юнит-тесты, надо быть в курсе некоторых практик разработки. Следование этим практикам максимально упростит как написание, так и поддержку тестов.

Наверняка вы слышали о принципах SOLID (если нет, то самое время ознакомиться). Обратим внимание на первый принцип. Вот что об этом принципе пишет википедия:

В объектно-ориентированном программировании принцип единственной обязанности (англ. Single responsibility principle) обозначает, что каждый объект должен иметь одну обязанность и эта обязанность должна быть полностью инкапсулирована в класс. Все его сервисы должны быть направлены исключительно на обеспечение этой обязанности.

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

Перейдем к внедрению зависимостей (DI). Посмотрим, что пишет об этом википедия:

Внедрение зависимости (англ. Dependency injection, DI) — процесс предоставления внешней зависимости программному компоненту. Является специфичной формой «инверсии управления» (англ. Inversion of control, IoC), где изменение порядка связи осуществляется путём получения необходимой зависимости.

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

Ок, теперь посмотрим в сторону Битрикса. Когда надо тестировать, а когда нет? Я не вижу никакого смысла применять модульное тестирование для простых проектов, где Битрикс большей степени используется как CMS. В таких случаях, на мой взгляд, лучше применить функционального тестирования. С другой стороны, есть крупные проекты с множеством интеграций со сторонними сервисами, сложной бизнес логикой, где Битрикс используется как фреймворк. В случае таких проектов использование модульного тестирования будет действительно эффективно.

Подведем промежуточные итоги.

Что нужно знать перед тем, как написать свой первый тест? Требуется понимание ООП и практика его использования. Если вы не используете ООП и хотите писать модульные тесты, у меня для вас плохие новости.

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

Существует такая величина как покрытие тестами, она показывает насколько полно вы покрыли каждый аспект поведения класса тестами. Покрытие в 100% это, конечно, круто. Но! Это не значит, что нужно всегда стремиться к этому значению. Не забывайте о времени, потраченном на написание тестов. Важно собственное понимание того, достаточно ли тестов вы написали. Это приходит с опытом.

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

А какие плюшки даст модульное тестирование?

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

К менее очевидному, но не менее важному моменту я отношу повышение качества ООП архитектуры. В чем это выражается? При проектировании класса приходится задумываться о том как сделать его тестируемым. Это ведет к тому, что классы становятся проще, менее связанными (coupling) и более связными (cohesion) в лучших традициях SOLID. Все это, конечно, приходит с опытом, но на мой взгляд, оно того стоит. С точки зрения производительности, разработчик начинающий писать тесты, несомненно будет тратить больше времени, чем на разработку без тестов. Но в перспективе уже опытный разработчик суммарно будет более производителен, т.к. тесты будут писаться очень быстро, а время на отлов багов значительно сократится.

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

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

Refactoring to collections или как заменить foreach коллекциями

Этот пост будет целиком и полностью посвящен книге «Refactoring to collections», написанной Adam Wathan. Читать дальше