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