Статьи

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

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

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

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

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

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

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

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

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

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

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


<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. Проверьте случай отрицательного баланса

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


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 :


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 . Изменения довольно небольшие, но после них юнит-тест становится зеленым.


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 :


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 на примере, который не слишком тривиален, но и не так сложен. В любом случае, надеюсь, это было полезно и информативно для вас.