Domain Object (рус. «Доменный объект») — один из наиболее популярных подходов к использованию тестовых данных непосредственно в логике скриптов. На данный момент является одним из самых популярных и распространенных approach'ей, благодаря своей простоте, понятности и логичности.
Применим во всех видах автоматизации функционального тестирования (End-to-End, API, Integration), в независимости от проверяемой платформы, будь то Web, Mobile, или Desktop.
Из другого названия подхода — «Business Object» — становится понятно, что это некая абстракция, представляющая собой модель и описание объекта, который важен именно для понимания и функционирования бизнес-логики приложения. Он не способен выполнять никаких функций, кроме «переноса» полей и их значений одного конкретного business-unit'а.
![image](https://habrastorage.org/r/w780q1/getpro/habr/post_images/787/fee/f37/787feef3760d3330f87aeec8c9802a55.jpg)
В качестве примера, возьмем любое приложение, где предусмотрено создание аккаунта пользователя. Именно пользователь и становится нашим доменным объектом. Практически во всех случаях, у юзера должны быть логин и пароль:
Особенно внимательные заметят, что все внутренние поля являются private. Задавать и считывать значения напрямую из полей объекта считается плохой практикой. Вместо этого принято использовать всем хорошо известные геттеры и сеттеры:
Теперь, мы получили доступ к полям, и можем задавать и считывать их значения. Но в данном примере у нас используется только два поля. А что произойдет, если этих полей будет пятнадцать? Верно, класс User разрастется до невиданных размеров, благодаря бесконечной копипасте getter'ов и setter'ов. Как же мы будем с этим справляться?
Здесь нам на помощь приходит Project Lombok — популярная библиотека, которая позволяет сократить код в несколько раз, избежать copy/paste-страданий, и значительно уменьшить количество времени на написание Data Object-классов. Несколько полезных ссылок:
Что же он делает? Автоматически создает геттеры и сеттеры для всех полей класса, путем присваивания ему соответствующей аннотации:
Просто, быстро, удобно. Таким образом, наш код становится гораздо читабельнее, классы — лаконичнее, а разработка — быстрее.
Хороший тест — это тест, в котором четко разделены тестовые данные, тестовая логика, и ее реализация.
Попробуем залогинить нашего пользователя. Создаем класс с тестовыми данными, и «наполняем» ими нового User'а:
Описываем модель Login-страницы:
Осталось только реализовать тестовый класс:
Готово! Вы великолепны!
В финале мы получаем аккуратную архитектуру проекта, с четким разделением логики, данных, и реализации. Lombok помогает избавиться от дублирования кода, Domain Object замечательно вписывается в философию Page Object, а поддержка кода становится сплошным удовольствием.
Должны же быть минусы?
Большое спасибо за внимание. Буду рад отзывам и критике.
P.S. Расскажите в комментариях, какие подходы вы используете в работе. Будет очень интересно почитать.
Применим во всех видах автоматизации функционального тестирования (End-to-End, API, Integration), в независимости от проверяемой платформы, будь то Web, Mobile, или Desktop.
ВАЖНО: не стоит путать Domain Object с Data Transfer Object (DTO). Это абсолютно разные подходы, которые применяются в разных сферах.В чем его суть?
Из другого названия подхода — «Business Object» — становится понятно, что это некая абстракция, представляющая собой модель и описание объекта, который важен именно для понимания и функционирования бизнес-логики приложения. Он не способен выполнять никаких функций, кроме «переноса» полей и их значений одного конкретного business-unit'а.
![image](https://habrastorage.org/getpro/habr/post_images/787/fee/f37/787feef3760d3330f87aeec8c9802a55.jpg)
Как это выглядит
В качестве примера, возьмем любое приложение, где предусмотрено создание аккаунта пользователя. Именно пользователь и становится нашим доменным объектом. Практически во всех случаях, у юзера должны быть логин и пароль:
// Создаем доменный класс User
public class User {
// Объявляем, что User может содержать значение Login
private String login;
// Объявляем, что User может содержать значение Password
private String password;
}
Особенно внимательные заметят, что все внутренние поля являются private. Задавать и считывать значения напрямую из полей объекта считается плохой практикой. Вместо этого принято использовать всем хорошо известные геттеры и сеттеры:
public class User {
private String login;
private String password;
// Интерфейс для присваивания значения полю login
public void setLogin(String login) {
this.login = login;
}
// Метод для получения значения login
public String getLogin() {
return this.login;
}
// Аналогично для пароля
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return this.password;
}
}
Теперь, мы получили доступ к полям, и можем задавать и считывать их значения. Но в данном примере у нас используется только два поля. А что произойдет, если этих полей будет пятнадцать? Верно, класс User разрастется до невиданных размеров, благодаря бесконечной копипасте getter'ов и setter'ов. Как же мы будем с этим справляться?
Магия Lombok
Здесь нам на помощь приходит Project Lombok — популярная библиотека, которая позволяет сократить код в несколько раз, избежать copy/paste-страданий, и значительно уменьшить количество времени на написание Data Object-классов. Несколько полезных ссылок:
Что же он делает? Автоматически создает геттеры и сеттеры для всех полей класса, путем присваивания ему соответствующей аннотации:
import lombok.Getter;
import lombok.Setter;
// Объявляем геттеры для всех полей класса User
@Getter
// Объявляем сеттеры для всех полей класса User
@Setter
public class User {
private String login;
private String password;
}
Просто, быстро, удобно. Таким образом, наш код становится гораздо читабельнее, классы — лаконичнее, а разработка — быстрее.
Применяем в тесте
Хороший тест — это тест, в котором четко разделены тестовые данные, тестовая логика, и ее реализация.
Попробуем залогинить нашего пользователя. Создаем класс с тестовыми данными, и «наполняем» ими нового User'а:
// Создаем Test Data класс
public class TestDataUser {
// Объявляем private-константы с тестовыми данными
private final static String DEFAULT_LOGIN = "vasiliy_pupkin";
private final static String DEFAULT_PASSWORD = "q1w2e3";
// Реализуем статический метод для получения дефолтного юзера
public static User getDefaultUser() {
// Создаем "чистого" пользователя
User user = new User();
// Используем сеттер для присваивания значения Login
user.setLogin(DEFAULT_LOGIN);
// Используем сеттер для присваивания значения Password
user.setPassword(DEFAULT_PASSWORD);
// Возвращаем объект класса User, наполненного нужными нам данными
return user;
}
}
Описываем модель Login-страницы:
public class LoginPage {
// Создаем public-метод логина, который принимает объект User
public void loginUser(User user) {
// Через геттер получаем логин и передаем его в метод enterLogin()
enterLogin(user.getLogin());
// Аналогично с паролем
enterPassword(user.getPassword());
}
private void enterLogin(String login) {
this.loginInput.sendKeys(login);
}
private void enterPassword(String password) {
this.passwordInput.sendKeys(password);
}
}
Осталось только реализовать тестовый класс:
public class LoginTest {
@Test
public void loginAsDefaultUser() {
// Получаем объект с credentials тестового юзера
User user = TestDataUser.getDefaultUser();
// Инициализируем Login-страницу
LoginPage loginPage = new LoginPage();
// Логинимся, используя ранее полученный объект
loginPage.loginUser(user);
}
}
Готово! Вы великолепны!
Подведем итоги
В финале мы получаем аккуратную архитектуру проекта, с четким разделением логики, данных, и реализации. Lombok помогает избавиться от дублирования кода, Domain Object замечательно вписывается в философию Page Object, а поддержка кода становится сплошным удовольствием.
Должны же быть минусы?
- Иммутабельность. А точнее, ее отсутствие. В этом данный подход с использованием Lombok проигрывает основном конкуренту — Builder (статья на Хабре). Зато, на мой взгляд, приведенный выше код является более «чистым», понятным, и эстетически приятным, по сравнению с нескончаемой цепочкой в билдере и нагромождением методов в Object-классе.
- Увеличение complexity там, где это не нужно. Не столько минус, сколько небольшое напоминание. Любой подход или паттерн имеет проблематику и должен решать некую проблему. Не стоит пытаться использовать Data Object в юнит-тесте, который всего лишь проверяет, что 2+2=4.
Большое спасибо за внимание. Буду рад отзывам и критике.
P.S. Расскажите в комментариях, какие подходы вы используете в работе. Будет очень интересно почитать.