Пожалуйста, опишите ошибку

Нашли баг? Помогите нам его исправить, заполнив эту форму

Test Driven Development: Заметки на полях.

Виталий Мороз
web-разработчик

Тестирование – неотъемлемая часть процесса разработки ПО. Оно происходит на всех стадиях жизненного цикла продукта. От идеи до его эксплуатации и поддержки. Но проводится тестирование продукта по-разному.

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

Техники TDD (Test Driven Development) и BDD (Behaviour Driven Development) – это как раз пример выполнения одно и того же процесса тестирования, но с разных точек зрения, то есть как и что нужно проверять в продукте.

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

«TDD – делать вещи правильно. BDD – делать правильные вещи». (©цитата)

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

Известно, что в 1959 ~ 1963 годах программисты для космической программы Меркурия уже использовали методы TDD при программировании на перфокартах.

В 1989 году Gerry Weinberg уже писал тесты для программ на перфокартах до их реализации. В этом же году Gary Goldberg также напишет по этой технике программу с финансовыми процессами на Smalltalk.

1995 году Kent Beck представляет подход TDD на форуме OopsLa (Object-Oriented Programming, Systems, Languages, and Applications), и в этом же году техника входит в концепт экстремального программирования.

В 2002 году выходит книга Test-Driven Development: By Example. Kent Beck.

А в марте 2006 года появляется первая статья от Dan North про BDD подход «Introducing BDD» в журнале Better Software.

Основные моменты

TDD (test-driven development) — техника разработки ПО, которая базируется на итерационном подходе через короткие циклы, состоящие из следующих шагов:

  1. Написать тест на необходимую функциональность.
  2. Реализовать код.
  3. Привести код к нужному стандарту (рефакторинг, оптимизация и тому подобное)

BDD (behavior-driven development) — расширение техники TDD, при котором особое внимание уделяется поведению системы/модуля в терминах бизнеса (заказчика).

Техника BDD состоит из тех же шагов, что и TDD, но еще фокусируется на следующих пунктах:

  1. Когда начат процесс.
  2. Что тестируется, а что нет.
  3. Как понять из-за чего тест не прошел.

Схематично шаги техник представляются так:

Сам тест в TDD, как правило, строится по шаблону AAA:

Arrange – подготовить все необходимые данные.

Act – выполнить действие.

Assert – проверить результат на выполнение.

Пример теста:

    
        @Test
        public void test() {
        String input = "abc";
        String result = Util.reverse(input);
        assertEquals("cba", result);
        }
    

Подход BDD имеет те же правила, но акцентирует внимание на сценарии:

As a [role] I want [feature] so that [benefit]. Given [initial context], when [event occurs], then [ensure some outcomes].

Пример вывода сценария:

Story: Returns go to stock

As a store owner

In order to keep track of stock

I want to add items back to stock when they’re returned.




Scenario 1: Refunded items should be returned to stock

Given that a customer previously bought a black sweater from me

And I have three black sweaters in stock.

When he returns the black sweater for a refund

Then I should have four black sweaters in stock.

Особенности

Изначально я хотел назвать данный пункт «Преимущество и недостатки», но это было бы не корректно, ведь при одинаковых ситуациях в разных командах может быть разный результат. Данная техника может подходить не всем и не всегда. Вот о таких особенностях мы и поговорим.

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

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

С другой стороны, если большая часть функциональности покрыта тестами (а при подходе TDD это гарантировано) вы без сомнений можете сказать, что система работает и оперирует данными правильно. При использовании TDD можно уверенно говорить, что покрытие кода тестами будет составлять 90 ~ 100%, а при использовании метода TLD оно скорее всего остановится примерно на 70 ~ 80%.

Не всегда есть возможность использовать TDD/BDD. Есть ряд ситуаций, когда это не подходит (человеческий фактор не в счет). Примерами могут служить взаимодействие front-end и back-end, проверка пользовательского интерфейса и работа с конфигурируемыми модулями.

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

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

Одним из явных плюсов использования TDD является то, что готовый проект будет спроектирован по принципам KISS (Keep it short and simple) и YAGNI (You ain’t gonna need it).

Так же можно отметить, что наименование теста при использовании BDD, как правило, совпадает или очень похоже на описание функций модуля или класса им проверяемых.

Пример

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

Задача – проверить, что введенный пароль был длиной от 5 до 10 символов. Пример будет на Java с использованием JUnit4.

Первое что делаем – пишем тест, где идет вызов функции проверки:

    
        import org.junit.Assert;
        import org.junit.Test;

        public class TestPassword {
            @Test
            public void TestPasswordLength() {
                Password password = new Password();

                Assert.assertFalse(password.isValid("1234"));
                Assert.assertTrue(password.isValid("1234qwerty"));
            }
        }

    

И сам класс с заглушкой для метода проверки.

    
        public class Password {
            public boolean isValid(String password) {
                return false;
            }
        }

    

Запустив тест, убедимся, что он у нас упал. Первый этап есть. Следует отметить, что данный шаг отличается от того, который приводится в базовой версии TDD. В нем позитивный и негативный сценарий, как правило, разделяется на два теста со своими итерациями. Для сокращения мы перепрыгнем через них.

Следующим шагом будет реализация самого метода.

    
        public class Password {
            public boolean isValid(String password) {
                if (password.length() > 4 && password.length() < 11) {
                    return true;
                } else {
                    return false;
                }
            }
        }

    

Убедимся, что тест проходит. После наступает стадия рефакторинга. Например, можно сделать метод статичным, чтобы не создавать новый объект каждый раз. При этом после изменения теста он упадет, так как в коде наш метод не статичный. Следовательно, мы уже приступили к новой итерации процесса.

    
        import org.junit.Assert;
        import org.junit.Test;

        public class TestPassword {
            @Test
            public void TestPasswordLength() {
                Assert.assertFalse(Password.isValid("1234"));
                Assert.assertTrue(Password.isValid("1234qwerty"));
            }
        }

    

Убедились, что тест не прошел, и приступаем к самой функции.

    
        public class Password {
            public static boolean isValid(String password) {
                return password.length() > 4 && password.length() < 11;
            }
        }

    

Единственное, что стоит помнить при каждой такой итерации, - если после написании нового теста у вас не появился красный флаг о том, что тест упал, то стоит насторожиться – возможно, функция выполняет не совсем то, что от неё ожидают.

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

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

Читать и комментировать