Статьи

Введение в Java TDD — часть 2

И снова здравствуйте! В предыдущем сообщении в блоге я объяснил в целом, без тесной ссылки на Java, но в этой части мы начнем практику TDD. Наша цель — пройти все этапы TDD: от анализа требований до рефакторинга тестируемого кода. Все это мы будем делать на примере с требованиями Java, JUnit и «поддельные».

Анализ требований

Давайте представим, что нам нужно создать новую функцию в вымышленном приложении. Функция описана в следующей пользовательской истории:

Как пользователь, я хочу иметь возможность создать учетную запись. Аккаунт должен содержать идентификатор, статус (активный / неактивный), зону и баланс. Свойство баланса не может быть отрицательным. По умолчанию аккаунт должен быть активным, в зоне № 1 и с балансом 0,00.

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

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

Ответ: Зоны используются в транзакциях приложения. В зависимости от зон мы взимаем разные комиссии со счетов. На данный момент мы планируем всего 3 зоны.

Хорошо. Теперь все ясно, и мы можем начать TDD.

Java TDD: первые тесты

Вот зависимости, которые нам нужны в проекте:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>org.javamoney</groupId>
            <artifactId>moneta</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-library</artifactId>
            <version>1.3</version>
        </dependency>
    </dependencies>

Я использую Maven, но вы также можете использовать Gradle или любой другой инструмент управления зависимостями. Наконец, мне нужно выполнить первый настоящий шаг разработки: создать пустой класс Account и соответствующий ему тестовый класс. Это структура проекта в Intellij IDEA:

TDD-проект-структура

Обратите внимание на местоположение класса Account и класс AccountTest . У них одинаковые имена пакетов, но разные каталоги. Это какое-то соглашение.

Вспоминая историю пользователей, я хочу создать следующие юнит-тесты:

  1. Создание учетной записи по умолчанию
  2. Создание пользовательского аккаунта
  3. Проверьте отрицательный баланс

Вот методы испытаний:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.model;
 
import org.javamoney.moneta.Money;
import org.junit.Test;
 
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
 
public class AccountTest {
 
    @Test
    public void defaultAccountCreationTest() {
        Account account = new Account();
        assertThat(account.getId().length(), equalTo(6));
        assertThat(account.getStatus(), equalTo(true));
        assertThat(account.getZone(), equalTo(Account.Zone.ZONE_1));
        assertThat(account.getBalance(), equalTo(Money.of(0.00, "USD")));
    }
 
    @Test
    public void customAccountCreationTest() {
        Account account = new Account(false, Account.Zone.ZONE_3, 125.95);
        assertThat(account.getId().length(), equalTo(6));
        assertThat(account.getStatus(), equalTo(false));
        assertThat(account.getZone(), equalTo(Account.Zone.ZONE_3));
        assertThat(account.getBalance(), equalTo(Money.of(125.95, "USD")));
    }
 
    @Test(expected = IllegalArgumentException.class)
    public void negativeBalanceTest() {
        Account account = new Account(false, Account.Zone.ZONE_3, -200);
    }
 
}

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

Реализация бизнес-логики

Когда юнит-тесты завершены, нам нужно увидеть что-то вроде этого в классе Account :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.model;
 
import org.javamoney.moneta.Money;
 
public class Account {
 
    private String id;
    private boolean status;
    private Zone zone;
    private Money balance;
 
    public Account() {}
 
    public Account(boolean status, Zone zone, double balance) {
 
    }
 
    public enum Zone {
        ZONE_1, ZONE_2, ZONE_3
    }
 
    public String getId() {
        return id;
    }
 
    public boolean getStatus() {
        return status;
    }
 
    public Zone getZone() {
        return zone;
    }
 
    public Money getBalance() {
        return balance;
    }
 
}

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

Когда мы запустим модульные тесты для класса Account, мы получим «красные» результаты. Таким образом, способ сделать их зелеными — начать с defaultAccountCreationTest () . В контексте этого теста мы должны обновить класс Account . Изменения довольно небольшие, но после них юнит-тест становится зеленым.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package com.model;
 
import org.apache.commons.lang3.RandomStringUtils;
import org.javamoney.moneta.Money;
 
public class Account {
 
    private String id = RandomStringUtils.randomAlphanumeric(6);
    private boolean status = true;
    private Zone zone = Zone.ZONE_1;
    private Money balance = Money.of(0.00, "USD");
 
    public Account() {}
//next code is omitted, it was not affected by the first changes

Вы можете запустить обновленный класс AccountTest . Результат запуска: один тест пройден, два не пройдены.
Затем нам нужно повторить эту операцию для каждого из модульных тестов, пока все они не станут «зелеными».

Это окончательная версия класса Account :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.model;
 
import org.apache.commons.lang3.RandomStringUtils;
import org.javamoney.moneta.Money;
 
public class Account {
 
    private String id = RandomStringUtils.randomAlphanumeric(6);
    private boolean status = true;
    private Zone zone = Zone.ZONE_1;
    private Money balance = Money.of(0.00, "USD");
 
    public Account() {}
 
    public Account(boolean status, Zone zone, double balance) {
        this.status = status;
        this.zone = zone;
        if (balance < 0)
            throw new IllegalArgumentException("The balance can not be negative");
        this.balance = Money.of(balance, "USD");
    }
 
    public enum Zone {
        ZONE_1, ZONE_2, ZONE_3
    }
 
    public String getId() {
        return id;
    }
 
    public boolean getStatus() {
        return status;
    }
 
    public Zone getZone() {
        return zone;
    }
 
    public Money getBalance() {
        return balance;
    }
 
}

И вот скриншот запуска тестов:

Тест-Заурядный результаты

После тестирования Рефакторинг

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

Резюме

В этом руководстве мы рассмотрели, как разрабатывать TDD с использованием Java , начиная с анализа возможностей и заканчивая «зелеными» модульными тестами и рефакторингом. Я попытался объяснить подход TDD на примере, который не слишком тривиален, но и не так сложен. В любом случае, надеюсь, это было полезно и информативно для вас.

Ссылка: Введение в Java TDD — часть 2 от нашего партнера по JCG Алексея Зволинского в блоге заметок Фрузенштейна .