JUnit :: або як полюбити валідатор JavaRush
Коротко про те, навіщо цей звір нам потрібний? JUnit - це фреймворк автоматичного тестування вашого хорошого чи не зовсім хорошого коду. Можна сказати: навіщо мені ця гойдалка, я і так зможу легко, і просто протестувати свій хороший Java код. Можна багато писати вступної лірики, але поет із мене ніякий, перейдемо краще до діла…Створюємо об'єкт
І так щоб щось тестувати, нам для початку потрібен об'єкт тестування. Перед нами стоїть завдання.- Нам потрібен об'єкт, який зберігатиме інформацію про Користувача.
- Id - потрібно рахувати по порядку додавання нового користувача.
- Ім'я користувача.
- Його вік.
- Підлога (male/female)
-
Потрібно передбачити збереження списку користувачів.
-
Клас має вміти.
- Формувати список усіх користувачів.
- Формувати список користувачів за статтю (MALE/FEMALE).
- Повертати кількість користувачів у загальному списку та порахувати кількість за ознакою статі користувача.
- Порахувати загальну суму за віком користувачів, так само врахувати за ознакою статі.
- Порахувати середній вік, як загальний і за ознакою статі.
User
він міститиме поля:
private int id;
private String name;
private int age;
private Sex sex;
Для зберігання даних про користувача цього достатньо, подивимося, що там ще потрібно за завданням. Нам потрібно якось зберігати всіх користувачів, зробимо в нашому класі статичне поле allUsers
, думаю нормально якщо це будеMap<Integer, User>
private static Map<Integer, User> allUsers;
Ще нам якось потрібно присвоювати порядковий номер користувачам, створимо статичне поле лічильник, який при створенні нового користувача присвоюватиме порядковий Id
користувачеві.
private static int countId = 0;
Так, з полями начебто розібралися, напишемо конструктор для нашого об'єкта, і гетери для полів id
, name
, age
, sex
. З гетерами там нічого складного немає, попросимо допомоги у IDEA , вона ніколи не відмовить, а конструктор зробимо трохи з хитрістю. Конструктор вмітиме. Ініціалізувати поля, перевіряти чи є такий об'єкт у allUsers
, якщо такого об'єкта немає, то збільшуємо наш лічильник countId++
і додаємо його до списку всіх користувачів. А також ініціалізувати поле allUsers
їли воно ще не було ініціалізоване. Для зручності пошуку однакових об'єктів, перевизначимо методи equals()
і hashCode()
знову попросимо допомоги у улюбленої IDEA і будемо порівнювати по полях name
, age
,sex
. Плюс створимо приватний метод hasUser()
, який перевірятиме чи є такий об'єкт у списку.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(name, user.name) &&
sex == user.sex;
}
@Override
public int hashCode() {
return Objects.hash(name, age, sex);
}
Конструктор зрештою у мене вийшов такий.
public User(String name, int age, Sex sex) {
if (allUsers == null){
allUsers = new HashMap<>();
}
this.name = name;
this.age = age;
this.sex = sex;
if (!hasUser()){
countId++;
this.id = countId;
allUsers.put(id, this);
}
}
та допоміжний приватний метод
private boolean hasUser(){
for (User user : allUsers.values()){
if (user.equals(this) && user.hashCode() == this.hashCode()){
return true;
}
}
return false;
}
а також перевизначимоtoString()
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
Тепер настав час реалізувати логіку необхідних методів. Так як логіка в основному працюватиме зі статичними полями, методи теж зробимо статичними, для об'єктів вони не потрібні.
- Формувати список усіх користувачів.
- Формувати список користувачів за статтю (MALE/FEMALE).
-
Повертати кількість користувачів у загальному списку та порахувати кількість за ознакою статі користувача.
public static int getHowManyUsers(){ return allUsers.size(); } public static int getHowManyUsers(Sex sex){ return getAllUsers(sex).size(); }
-
Порахувати загальну суму за віком користувачів, так само врахувати за ознакою статі. Для цього завдання зробимо методи.
public static int getAllAgeUsers(){ int countAge = 0; for (User user : allUsers.values()){ countAge += user.age; } return countAge; } public static int getAllAgeUsers(Sex sex){ int countAge = 0; for (User user : getAllUsers(sex)){ countAge += user.age; } return countAge; }
-
Порахувати середній вік, як загальний і за ознакою статі.
public static int getAverageAgeOfAllUsers(){ return getAllAgeUsers() / getHowManyUsers(); } public static int getAverageAgeOfAllUsers(Sex sex){ return getAllAgeUsers(sex) / getHowManyUsers(sex); }
Відмінно, необхідний об'єкт та її поведінка ми описали. Тепер можна переходити до JUnit , але для початку покажу як приблизно буде виглядати простий тест, якщо ми його буде робити в main .
public static void main(String[] args) { new User("Євген", 35, Sex.MALE); new User("Марина", 34, Sex.FEMALE); new User("Аліна", 7, Sex.FEMALE); System.out.println("Всі користувачі:"); User.getAllUsers().forEach(System.out::println); System.out.println("Всі користувачі: MALE"); User.getAllUsers(Sex.MALE).forEach(System.out::println); System.out.println("Всі користувачі: FEMALE"); User.getAllUsers(Sex.FEMALE).forEach(System.out::println); System.out.println("================================================"); System.out.println(" всіх користувачів: " + User.getHowManyUsers()); System.out.println(" всіх користувачів MALE: " + User.getHowManyUsers(Sex.MALE)); System.out.println("всіх користувачів FEMALE: " + User.getHowManyUsers(Sex.FEMALE)); System.out.println("================================================"); System.out.println(" загальний вік всіх користувачів: " + User.getAllAgeUsers()); System.out.println(" загальний вік всіх користувачів MALE: " + User.getAllAgeUsers(Sex.MALE)); System.out.println("загальний вік усіх користувачів FEMALE: " + User.getAllAgeUsers(Sex.FEMALE)); System.out.println("================================================"); System.out.println(" середній вік всіх користувачів: " + User.getAverageAgeOfAllUsers()); System.out.println(" середній вік всіх користувачів MALE: " + User.getAverageAgeOfAllUsers(Sex.MALE)); System.out.println("середній вік усіх користувачів FEMALE: " + User.getAverageAgeOfAllUsers(Sex.FEMALE)); System.out.println("================================================"); }
Висновок у консоль отримаємо приблизно такий, а далі порівнюємо чи ми отримали нормальну роботу. Можна, звичайно, заглибитися, написати логіку порівняння, і подивитися, що скаже наше обчислення, при тому, що ми не будемо впевнені, що всі змогли передбачити.
//output Все пользователи: User{id=1, name='Євген', age=35, sex=MALE} User{id=2, name='Марина', age=34, sex=FEMALE} User{id=3, name='Аліна', age=7, sex=FEMALE} Все пользователи: MALE User{id=1, name='Євген', age=35, sex=MALE} Все пользователи: FEMALE User{id=2, name='Марина', age=34, sex=FEMALE} User{id=3, name='Аліна', age=7, sex=FEMALE} ================================================ всех пользователей: 3 всех пользователей MALE: 1 всех пользователей FEMALE: 2 ================================================ общий возраст всех пользователей: 76 общий возраст всех пользователей MALE: 35 общий возраст всех пользователей FEMALE: 41 ================================================ средний возраст всех пользователей: 25 средний возраст всех пользователей MALE: 35 средний возраст всех пользователей FEMALE: 20 ================================================ Process finished with exit code 0
Нас цей результат не влаштовує, геть тести main , нам потрібен JUnit .
З пунктами a і b добре впорається метод getAllUsers()
який повертатиме аркуш всіх User
, і перевантажений метод getAllUsers(Sex sex)
він повертатиме список, залежно від переданого параметра Sex
.
public static List<User> getAllUsers(){
return new ArrayList<>(allUsers.values());
}
public static List<User> getAllUsers(Sex sex){
List<User> listAllUsers = new ArrayList<>();
for (User user : allUsers.values()){
if (user.sex == sex){
listAllUsers.add(user);
}
}
return listAllUsers;
}
Як підключити JUnit до проекту
Постає питання, як його підключити до проекту. Для знаючих варіант з Maven брати не буду, тому що це зовсім інша історія. ;) Відкриваємо структуру проекту Ctrl + Alt + Shift + S -> Libraries -> тиснемо + (New Project Library) -> вибираємо from Maven далі бачимо таке вікно, в рядок пошуку вводимо “ junit:junit:4.12 ” чекаємо поки знайде -> ОК! -> OK! Повинен вийти такий результат Тиснемо OK, привітаю JUnit доданий до проекту. Їдемо далі. Тепер нам потрібно створити тести для нашого Java класу, ставимо курсор на назву класуUser
-> тиснемо Alt + Enter -> вибираємо create Test. Ми повинні побачити вікно, в якому нам потрібно вибрати бібліотеку JUnit4 -> вибрати методи, які збираємося тестувати -> OK Ідея сама створить класUserTest
Це і є клас, в якому ми будемо покривати наш код тестами. Приступимо:
Наш перший @Test
Створимо наш перший @Test методgetAllUsers()
- це метод який повинен повернути всіх користувачів. Тест виглядатиме приблизно так:
@Test
public void getAllUsers() {
//створюємо тестові дані
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
//створюємо список expected та заповнюємо його даними нашого методу
List<User> expected = User.getAllUsers();
//створюємо список actual в нього розміщуємо дані для порівняння
//що ми припускаємо метод повинен повернути
List<User> actual = new ArrayList<>();
actual.add(user);
actual.add(user1);
actual.add(user2);
//запускаємо тест, якщо список expected і actual не будуть рівні
//тест буде провалений, про результати тесту читаємо у консолі
Assert.assertEquals(expected, actual);
}
Тут ми створюємо кілька тестових користувачів -> створюємо список expected
в який помістимо користувачів яких нам поверне метод getAllUsers()
-> створимо список actual
в який помістимо користувачів яких ми припускаємо, що метод getAllUsers()
Assert.assertEquals(actual, expected) йому ми і передамо списки, що інспектується та актуальний. Цей метод перевірить об'єкти у наданих списках та видасть результат тесту. Метод порівнюватиме всі поля об'єктів, навіть пройдеться полями батьків, якщо є успадкування. Запускаємо перший тест... Тест виконано успішно. actual
Тепер спробуємо зробити так, щоб тест був провалений, для цього нам потрібно змінити один зі списків тесту, зробимо це шляхом закоментувавши додавання одного користувача до списку
@Test
public void getAllUsers() {
//створюємо тестові дані
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
//створюємо список expected та заповнюємо його даними нашого методу
List<User> expected = User.getAllUsers();
//створюємо список actual в нього розміщуємо дані для порівняння
//що ми припускаємо метод повинен повернути
List<User> actual = new ArrayList<>();
actual.add(user);
actual.add(user1);
//actual.add(user2);
//запускаємо тест, якщо список expected і actual не будуть рівні
//тест буде провалений, про результати тесту читаємо у консолі
Assert.assertEquals(expected, actual);
}
запускаємо тест і бачимо наступне: Тепер ми можемо трохи розібрати причину провалу тесту. Тут ми бачимо, що в списку, що інспектується, більше користувачів ніж в актуальному. Це і є причиною провалу. А у main ми можемо перевірити так? JUnit : main = 1 : 0. Давайте подивимося як виглядатиме тест, якщо в ньому будуть повністю різні об'єкти, зробимо це так:
@Test
public void getAllUsers() {
//створюємо тестові дані
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
//створюємо список expected та заповнюємо його даними нашого методу
List<User> expected = User.getAllUsers();
//створюємо список actual в нього розміщуємо дані для порівняння
//що ми припускаємо метод повинен повернути
List<User> actual = new ArrayList<>();
actual.add(new User("User1", 1, Sex.MALE));
actual.add(new User("User2", 2, Sex.FEMALE));
actual.add(new User("User3", 3, Sex.MALE));
//запускаємо тест, якщо список expected і actual не будуть рівні
//тест буде провалений, про результати тесту читаємо у консолі
Assert.assertEquals(expected, actual);
}
ось що буде в консолі: тут відразу видно що в порівнюваних списках різні користувачі, ще ми можемо натиснути на <Click to see difference> отримаємо таке вікно, де можна подивитися докладно з якими даними у нас проблема. IDEA підсвітить усі поля, у яких є відмінності. main
таке може? - Ні. JUnit : main = 2 : 0 Ну що, підемо далі у нас ще купа методів, які потрібно покрити тестами ), але зачекайте, а буде не погано, перевірити, а чи не буде нам метод повертати , адже приблизно так нас getAllUsers()
на null
задачах JavaRush ловить валідатор). Зробимо це, ділиш то на три копійки…
@Test
public void getAllUsers_NO_NULL() {
//додамо перевірку на null
List<User> expected = User.getAllUsers();
Assert.assertNotNull(expected);
}
Так, так приблизно так валідатор ловить наш null
;) Тепер запустимо цей тест, і подивимося, що він нам покаже. А покаже він помилку, як???? як же тут можна було припуститися помилки тесту))) І тут ми можемо пожинати перші плоди покриття свого коду тестами. Як ви пам'ятаєте, поле allUsers
ми ініціалізували в конструкторі, і отже при виклику методу getAllUsers()
ми звернемося до об'єкта, який ще не був ініціалізований. Правитимемо, приберемо ініціалізацію з конструктора, і зробимо її при оголошенні поля.
private static Map<Integer, User> allUsers = new HashMap<>();
public User(String name, int age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
if (!hasUser()) {
countId++;
this.id = countId;
allUsers.put(id, this);
}
}
Запустимо тест, тепер усе гаразд. не думаю що в main легко буде відловити NPE, думаю ви погодитеся що рахунок JUnit : main = 3 : 0 Далі я всі методи покрию тестами, і дам вам подивитися, як це виглядатиме... Тепер клас тестів у нас виглядає так:
package user;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
public class UserTest {
@Test
public void getAllUsers() {
//створюємо тестові дані
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
//створюємо список expected та заповнюємо його даними нашого методу
List<User> expected = User.getAllUsers();
//створюємо список actual в нього розміщуємо дані для порівняння
//що ми припускаємо метод повинен повернути
List<User> actual = new ArrayList<>();
actual.add(user);
actual.add(user1);
actual.add(user2);
//запускаємо тест, якщо список expected і actual не будуть рівні
//тест буде провалений, про результати тесту читаємо у консолі
Assert.assertEquals(expected, actual);
}
@Test
public void getAllUsers_NO_NULL() {
//додамо перевірку на null
List<User> expected = User.getAllUsers();
Assert.assertNotNull(expected);
}
@Test
public void getAllUsers_MALE() {
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
List<User> expected = User.getAllUsers(Sex.MALE);
List<User> actual = new ArrayList<>();
actual.add(user);
Assert.assertEquals(expected, actual);
}
@Test
public void getAllUsers_MALE_NO_NULL() {
//додамо перевірку на null
List<User> expected = User.getAllUsers(Sex.MALE);
Assert.assertNotNull(expected);
}
@Test
public void getAllUsers_FEMALE() {
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
List<User> expected = User.getAllUsers(Sex.FEMALE);
List<User> actual = new ArrayList<>();
actual.add(user1);
actual.add(user2);
Assert.assertEquals(expected, actual);
}
@Test
public void getAllUsers_FEMALE_NO_NULL() {
//додамо перевірку на null
List<User> expected = User.getAllUsers(Sex.FEMALE);
Assert.assertNotNull(expected);
}
@Test
public void getHowManyUsers() {
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
int expected = User.getHowManyUsers();
int actual = 3;
Assert.assertEquals(expected, actual);
}
@Test
public void getHowManyUsers_MALE() {
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
int expected = User.getHowManyUsers(Sex.MALE);
int actual = 1;
Assert.assertEquals(expected, actual);
}
@Test
public void getHowManyUsers_FEMALE() {
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
int expected = User.getHowManyUsers(Sex.FEMALE);
int actual = 2;
Assert.assertEquals(expected, actual);
}
@Test
public void getAllAgeUsers() {
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
int expected = User.getAllAgeUsers();
int actual = 35 + 34 + 7;
Assert.assertEquals(expected, actual);
}
@Test
public void getAllAgeUsers_MALE() {
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
int expected = User.getAllAgeUsers(Sex.MALE);
int actual = 35;
Assert.assertEquals(expected, actual);
}
@Test
public void getAllAgeUsers_FEMALE() {
User user = new User("Євген", 35, Sex.MALE);
User user1 = new User("Марина", 34, Sex.FEMALE);
User user2 = new User("Аліна", 7, Sex.FEMALE);
int expected = User.getAllAgeUsers(Sex.FEMALE);
int actual = 34 + 7;
Assert.assertEquals(expected, actual);
}
}
Та не маленький вийшов, а що буде при роботі з великими проектами. Що ж тут можна скоротити, оцінивши все, можна помітити, що тестові дані ми створюємо в кожному тесті, і тут нам на допомогу приходять анотації. Візьмемо @Before
— Анотація @Before
вказує на те, що метод буде виконуватися перед кожним методом, що тестується @Test
. Ось так тепер буде виглядати наш клас тестів з анотацією @Before
:
package user;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
public class UserTest {
private User user;
private User user1;
private User user2;
@Before
public void setUp() throws Exception {
user = new User("Євген", 35, Sex.MALE);
user1 = new User("Марина", 34, Sex.FEMALE);
user2 = new User("Аліна", 7, Sex.FEMALE);
}
@Test
public void getAllUsers() {
List<User> expected = User.getAllUsers();
List<User> actual = new ArrayList<>();
actual.add(user);
actual.add(user1);
actual.add(user2);
Assert.assertEquals(expected, actual);
}
@Test
public void getAllUsers_NO_NULL() {
List<User> expected = User.getAllUsers();
Assert.assertNotNull(expected);
}
@Test
public void getAllUsers_MALE() {
List<User> expected = User.getAllUsers(Sex.MALE);
List<User> actual = new ArrayList<>();
actual.add(user);
Assert.assertEquals(expected, actual);
}
@Test
public void getAllUsers_MALE_NO_NULL() {
//додамо перевірку на null
List<User> expected = User.getAllUsers(Sex.MALE);
Assert.assertNotNull(expected);
}
@Test
public void getAllUsers_FEMALE() {
List<User> expected = User.getAllUsers(Sex.FEMALE);
List<User> actual = new ArrayList<>();
actual.add(user1);
actual.add(user2);
Assert.assertEquals(expected, actual);
}
@Test
public void getAllUsers_FEMALE_NO_NULL() {
//додамо перевірку на null
List<User> expected = User.getAllUsers(Sex.FEMALE);
Assert.assertNotNull(expected);
}
@Test
public void getHowManyUsers() {
int expected = User.getHowManyUsers();
int actual = 3;
Assert.assertEquals(expected, actual);
}
@Test
public void getHowManyUsers_MALE() {
int expected = User.getHowManyUsers(Sex.MALE);
int actual = 1;
Assert.assertEquals(expected, actual);
}
@Test
public void getHowManyUsers_FEMALE() {
int expected = User.getHowManyUsers(Sex.FEMALE);
int actual = 2;
Assert.assertEquals(expected, actual);
}
@Test
public void getAllAgeUsers() {
int expected = User.getAllAgeUsers();
int actual = 35 + 34 + 7;
Assert.assertEquals(expected, actual);
}
@Test
public void getAllAgeUsers_MALE() {
int expected = User.getAllAgeUsers(Sex.MALE);
int actual = 35;
Assert.assertEquals(expected, actual);
}
@Test
public void getAllAgeUsers_FEMALE() {
int expected = User.getAllAgeUsers(Sex.FEMALE);
int actual = 34 + 7;
Assert.assertEquals(expected, actual);
}
}
Ну як вам, вже веселіше та легше читати ;) Ось список анотацій для JUnit з ними однозначно жити простіше.
@Test – определяет что метод method() является тестовым.
@Before – указывает на то, что метод будет выполнятся перед каждым тестируемым методом @Test.
@After – указывает на то что метод будет выполнятся после каждого тестируемого метода @Test
@BeforeClass – указывает на то, что метод будет выполнятся в начале всех тестов,
а точней в момент запуска тестов(перед всеми тестами @Test).
@AfterClass – указывает на то, что метод будет выполнятся после всех тестов.
@Ignore – говорит, что метод будет проигнорирован в момент проведения тестирования.
(expected = Exception.class) – указывает на то, что в данном тестовом методе
вы преднамеренно ожидаете Exception.
(timeout = 100) – указывает, что тестируемый метод не должен занимать больше чем 100 миллисекунд.
Основні методи класу Assert
для перевірки:
fail(String) – указывает на то что бы тестовый метод завалился при этом выводя текстовое сообщение.
assertTrue([message], boolean condition) – проверяет, что логическое умова истинно.
assertsEquals([String message], expected, actual) – проверяет, что два значения совпадают.
Примечание: для массивов проверяются ссылки, а не содержание массивов.
assertNull([message], object) – проверяет, что об'єкт является пустым null.
assertNotNull([message], object) – проверяет, что об'єкт не является пустым null.
assertSame([String], expected, actual) – проверяет, что обе переменные относятся к одному об'єкту.
assertNotSame([String], expected, actual) – проверяет, что обе переменные относятся к разным об'єктам.
Ось так ми можемо додати залежність JUnit 4.12 у Maven
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
продовження тут -> JUnit part II
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ