Java. Тестування JUnit. Приклад

Тестування JUnit. Приклади


Зміст


Пошук на інших ресурсах:

1. Поняття про модульне тестування в Java

У системах програмування на мові Java, зокрема Java Eclipse, введено засоби тестування, які передбачають створення та використання автоматичних тестів для розроблених програм. Правильно побудоване тестування дозволяє звести кількість помилок у програмі до нуля.

При розробці сучасних програмних систем популярною є методологія TDD (Test Driven Development). Основна ідея застосування TDD полягає в тому, що спочатку розробляються тести на бажану поведінку програми (системи), а потім пишеться робочий програмний код, який з допомогою засобів рефакторингу перевіряється вже створеними тестами. Як наслідок, наперед підготовлені тести дозволяють уникнути здачі в експлуатацію програми, що містить помилки.

Для проведення тестування в Java Eclipse застосовується так зване JUnit-тестування, яке є фактичним стандартом для тестування Java-додатків. JUnit – це просте середовище (framework), яке дозволяє писати тести у вигляді класів на мові Java. Ці класи можуть запускатись як єдине ціле з допомогою засобу виконання тестів.

JUnit має багато розширень, які впроваджені у всіх сучасних середовищах розробки на мові Java. На сьогоднішній день JUnit використовується у таких напрямках як:

  • модульне тестування;
  • тестування веб-сайтів;
  • автоматична підстановка інтерфейсів для тестування;
  • тестування паралельного виконання додатків;
  • тестування продуктивності.

JUnit-тестування використовується багатьма групами тестувальників (quality assurance) на спеціально-розроблених веб-сайтах, які забезпечують автоматизацію застосування наскрізних функціональних тестів.

В Java Eclipse використання механізму JUnit не потребує працюючого серверу додатків або діючої бази даних.

 

2. Поняття тестового прикладу (Test Case). Клас, що містить тестові приклади (методи). Запуск тесту

При використання механізму JUnit, розглядаються дві основні одиниці (класи):

  • клас, методи якого потрібно протестувати;
  • клас, який представляє собою JUnit тест. Цей клас побудований за спеціальними правилами.

Отже, JUnit-тест представляє собою спеціально розроблений клас, що містить методи, які тестують програмний код інших класів. У системі Java Eclipse цей клас створюється з допомогою команди JUnit Test Case (дивіться приклад нижче).

Клас містить ряд методів. Кожен з методів тестувального класу розглядається як окремий тестовий прикладTest Case.

Тестові приклади (методи) JUnit-класу можуть бути оголошені з використанням наступних анотацій (розглядається версія Java Eclipse 2018-09):

  • @BeforeAll – статичний метод (тестовий приклад), що оголошений з цією анотацією, викликається на початку тестування;
  • @AfterAll – статичний метод, що оголошений з цією анотацією, викликається в кінці тестування. Тут можна задавати, наприклад, звільнення ресурсів що використовуються при тестуванні;
  • @Test – метод, який оголошений з цією анотацією, є тестовим прикладом. Він містить безпосередньо код тестування. Цей метод використовує методи класу org.junit.jupiter.api.Assertions. У цьому методі використовуються методи порівняння, які починаються з префіксу assert*(). Наприклад, для порівняння двох значень будь-якого примітивного типу використовується перевантажений метод assertEquals(). В одному тестувальному класі JUnit може бути довільна кількість методів (прикладів) тестування, які оголошені з анотацією @Test;
  • @BeforeEach – викликається перед викликом кожного тестового прикладу (методу), який оголошений з анотацією @Test;
  • @AfterEach – викликається після завершення кожного тестового прикладу (методу), який оголошений з анотацією @Test.

Допускається довільна кількість методів з усіма вищенаведеними анотаціями.

У найбільш загальному випадку, приблизний вигляд класу, що згенерований системою Java Eclipse, може бути таким

class TestClass {

  @BeforeAll
  static void setUpBeforeClass() throws Exception {
  }

  @AfterAll
  static void tearDownAfterClass() throws Exception {
  }

  @BeforeEach
  void setUp() throws Exception {
  }

  @AfterEach
  void tearDown() throws Exception {
  }

  @Test
  void testMethod() {
    fail("Not yet implemented");
  }
}

У методі testMethod() потрібно вставити власний код перевірки роботи методів іншого класу (дивіться приклади нижче). Після цього, можна запускати тест з допомогою спеціальної команди Java Eclipse (дивіться приклад нижче).

 

3. Приклад, що демонструє використання JUnit для перевірки правильності розв’язку квадратного рівняння. Покрокова інструкція
3.1. Умова задачі

Використовуючи технологію JUnit розробити Unit-тест для перевірки роботи класу SquareEquation, який містить засоби розв’язку квадратного рівняння.

Тест повинен розміщуватись у класі SquareEquationTest та містити відповідні методи перевірки правильності отриманого розв’язку.

Корені квадратного рівняння повертаються у вигляді екземпляру класу Roots.

Клас SquareEquationTest містить відповідні засоби розв’язку квадратного рівняння.

 

3.2. Рішення
3.2.1. Створення класів SquareEquation та Roots

Перед створенням класів створюється проект стандартним для Java Eclipse способом. У проект додаються два класи з іменами SquareEquation та Roots та формується код цих класів. Відповідно створюється файл SquareEquation.java. Після виконаних дій, вікно Java Eclipse має вигляд як показано на рисунку 1.

Вікно Java Eclipse. Класи Roots та SquareEquation

Рисунок 1. Вікно Java Eclipse. Класи Roots та SquareEquation

Класи мають наступне призначення:

  • Roots – використовується для збереження коренів квадратного рівняння (x1, x2) у випадку, якщо рівняння має ці корені;
  • SquareEquation – реалізує методи розв’язку квадратного рівняння.

Клас SquareEquation має наступні складові:

  • внутрішні змінні a, b, c що є коефіцієнтами квадратного рівняння;
  • конструктор, що ініціалізує значення внутрішніх полів a, b, c;
  • метод Solution(), який повертає екземпляр класу Roots у випадку, якщо рівняння має корені. Якщо рівняння не має коренів, то генерується виключення типу ArithmeticException.

Повний текст класів Roots та SquareEquation виглядає так

// Корені рівняння
class Roots {
  public double x1;
  public double x2;
}

// Клас, який розв'язує квадратне рівняння - цей клас буде протестовано іншим класом.
// a*x^2 + b*x + c = 0
public class SquareEquation {

  // Коефіцієнти рівняння
  private double a, b, c;

  // Конструктор
  public SquareEquation(double a, double b, double c) {
    this.a = a; this.b = b; this.c = c;
  }

  // Метод, що розв'язує квадратне рівняння
  public Roots Solution() {
    // Дискримінант
    double d = b*b - 4*a*c;

    // Перевірка, чи рівняння має корені
    if (d<0)
      throw new ArithmeticException("Solution has no roots.");

    // Обчислити результат
    Roots result = new Roots();
    result.x1 = (-b - Math.sqrt(d)) / (2*a);
    result.x2 = (-b + Math.sqrt(d)) / (2*a);

    // Повернути результат
    return result;
  }
}

Після створення класів можна переходити до створення Unit-тесту.

 

3.2.2. Створення класу, необхідного для реалізації JUnit тестування

Для створення Unit-тесту в Java Eclipse потрібно вибрати послідовність команд

File->New->JUnit Test Case

як показано на рисунку 2.

Java Eclipse. Команда створення Unit-тесту в Java

Рисунок 2. Команда створення Unit-тесту в Java

У результаті відкриється вікно “New JUnit Test Case” (рисунок 3) в якому потрібно вказати необхідну інформацію про тест.

Java Eclipse. JUnit-тестування. Вікно створення нового тестуРисунок 3. Вікно створення нового тесту. Задавання параметрів тесту

При створенні вікна система автоматично заповнить деякі поля. У вікні вказується така інформація.

  1. У полі Source Folder вказується ім’я папки з вихідними файлами проекту. Система автоматично “підтягує” ім’я папки з поточним проектом.
  2. У полі Name – задається ім’я класу, в якому будуть розміщуватись методи тестування (з анотаціями @BeforeAll, @Test тощо). У нашому випадку пропонується SquareEquationTest (до імені класу SquareEquation додається закінчення Test). Запропоноване ім’я можна залишити без змін.
  3. У полі “Which method stubs would you like to create?” з допомогою опцій вказуються методи, які потрібно сформувати в класі. Відповідно до вибраної опції формуються наступні методи:
    • setUpBeforeClass() – ім’я методу з анотацією @BeforeAll (дивіться п.2);
    • setUp() – ім’я методу з анотацією @BeforeEach;
    • tearDownAfterClass() – ім’я методу з анотацією @AfterAll;
    • tearDown() – ім’я методу з анотацією @AfterEach.

Для нашого тесту достатньо активувати тільки одне поле setUp(). Це дасть змогу додати метод з анотацією @BeforeEach, що буде викликатись перед тестом. У цьому методі буде створюватись екземпляр класу SquareEquation (дивіться коди нижче).

  1. У полі “Class under test:” вказується ім’я класу SquareEquation, методи якого потрібно протестувати.
  2. Додатково задаються інші опції:
  • вибір моделі тестування “New JUnit Jupiter test”;
  • можливість додавання коментарів;
  • інше.

У нашому випадку можна залишити все без змін.

Після вибору кнопки “Next >” відкриється наступне вікно “New JUnit Test Case” в якому потрібно задати перелік методів, які будуть тестуватись. Вигляд вікна зображено на рисунку 4. Вибрані методи будуть оголошуватись з анотацією @Test. Це є безпосередньо тестові приклади. Всі інші опції у вікні можна залишити за замовчуванням і перейти далі з допомогою кнопки Finish.

Java Eclipse. JUnit-тестування. Вибір методів, що будуть тестуватисьРисунок 4. Вибір методів, що будуть тестуватись. Вибрано метод Solution()

 

3.2.3. Код класу SquareEquationTest

Після створення JUnit тесту з допомогою віконного інтерфейсу Java Eclipse згенерує код класу SquareEquationTest

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class SquareEquationTest {

  @BeforeEach
  void setUp() throws Exception {
  }

  @Test
  void testSolution() {
    fail("Not yet implemented");
  }
}

У цьому коді сформовано два методи:

  • метод setUp() з анотацією @BeforeEach. Цей метод буде викликатись першим при тестуванні;
  • метод testSolution() з анотацією @Test. У цьому методі буде поміщено безпосередньо тест функції Solution() класу SquareEquation.

 

3.2.4. Модифікація класу SquareEquationTest. Програмування тесту

Щоб протестувати роботу класу SquareEquation потрібно створити екземпляр цього класу. Тому, в текст класу SquareEquationTest додатково вводиться посилання на клас SquareEquation

class SquareEquationTest {

  private SquareEquation se;

  ...
}

Потім у методі setUp() створюється екземпляр класу SquareEquation, на який вказує посилання se

@BeforeEach
void setUp() throws Exception {
  // Цей метод викликається першим.
  // Створити екземпляр класу SquareEquation.
  se = new SquareEquation(2, 1, -3); // 2*x^2 + x - 3 = 0
}

Після цього, в методі TestSolution() вписується код перевірки правильності отриманого розв’язку для заданих коефіцієнтів

a = 2
b = 1
c = -3

в екземплярі se

@Test
void testSolution() {
  // Оголосити екземпляр класу Roots
  Roots rt = se.Solution();

  // Перевірка розв'язку x1
  assertEquals(rt.x1, -1.5);

  // Перевірка розв'язку x2
  assertEquals(rt.x2, 1.0);
}

Перевірка розв’язку здійснюється з допомогою перевантаженого методу assertEquals() класу org.junit.jupiter.api.Assertions. Відбувається порівняння розв’язку в змінних rt.x1 та rt.x2 з наперед обчисленим (вручну) розв’язком поточного варіанту (a = 2, b = 1, c = -3) квадратного рівняння. Якщо є співпадіння, то розв’язок правильний. Про те, що розв’язок правильний, потрібна сказати система після запуску тесту (наступний пункт).

В цілому текст всього модуля тестування наступний

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class SquareEquationTest {
  // Посилання на клас SquareEquation
  private SquareEquation se;

  @BeforeEach
  void setUp() throws Exception {
    // Цей метод викликається першим.
    // Створити екземпляр класу SquareEquation
    se = new SquareEquation(2, 1, -3); // 2*x^2 + x - 3 = 0
  }

  @Test
  void testSolution() {
    // Оголосити екземпляр класу Roots
    Roots rt = se.Solution();

    // Перевірка розв'язку x1
    assertEquals(rt.x1, -1.5);

    // Перевірка розв'язку x2
    assertEquals(rt.x2, 1.0);
  }
}

 

3.3. Запуск тесту засобами Java Eclipse. Перегляд результату тестування

Після того, як сформовано код класу SquareEquationTest, розроблений тест можна запускати на виконання. Це здійснюється командами

Run -> Run As -> JUnit Test

або викликом послідовності комбінацій клавіш Alt+Shift+X, T як показано на рисунку 5.

Java Eclipse. Запуск JUnit-тесту

Рисунок 5. Запуск JUnit-тесту

Існує інший спосіб з допомогою команди швидкого меню (рисунок 6).

Java Eclipse. Команда запуску JUnit-тесту

Рисунок 6. Команда запуску JUnit-тесту

Після запуску у лівій частині Java Eclipse у вікні JUnit відобразиться результат тестування (рисунок 7).

Java Eclipse. Результат JUnit-тестування

Рисунок 7. Результат тестування

Як видно з результату, отримано підтвердження правильного розв’язку, тобто тест складено. Отже, метод Solution() класу SquareEquation дає правильний результат для випадку коли a = 2, b = 1, c = -3.

Якщо в методі TestSolution() при виклику assertEquals() навмисно вказати невірну відповідь, наприклад

assertEquals(rt.x2, 777.777);

то після повторного запуску тесту, у вікні JUnit відобразиться помилка як видно на рисунку 8. Хоча це не є помилка програми, а навмисна помилка в тесті.

Java Eclipse. JUnit-тестування. Демонстрація провалу тестуРисунок 8. Демонстрація провалу тесту

 

4. Приклад, що демонструє послідовність виклику методів у JUnit-тесті

Нехай в системі Java Eclipse створено клас з іменем TestClass для проведення тестування деякого іншого класу. У даному прикладі, суть класу, що тестується, не має значення.

Програмний код класу наступний

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class TestClass {

  @BeforeAll
  static void setUpBeforeClass() throws Exception {
    System.out.println("BeforeAll");
  }

  @BeforeAll
  static void setUpBeforeClass2() throws Exception {
    System.out.println("BeforeAll2");
  }

  @AfterAll
  static void tearDownAfterClass() throws Exception {
    System.out.println("AfterAll");
  }

  @BeforeEach
  void setUp() throws Exception {
    System.out.println("BeforeEach");
  }

  @BeforeEach
  void setUp2() throws Exception {
    System.out.println("BeforeEach2");
  }

  @AfterEach
  void tearDown() throws Exception {
    System.out.println("AfterEach");
  }

  @Test
  void testSolution() {
    System.out.println("Test");
  }

  @Test
  void testSolution2() {
    System.out.println("Test2");
  }
}

Після запуску тесту на виконання, програма видасть наступний результат

BeforeAll
BeforeAll2
BeforeEach2
BeforeEach
Test
AfterEach
BeforeEach2
BeforeEach
Test2
AfterEach
AfterAll

Проаналізувавши цей результат, можна прийти до висновку, що методи в тестувальному класі викликаються у порядку, що визначається їх анотаціями. Найпершими викликаються методи з анотацією @BeforeAll.

Наступними розглядаються тестові приклади, що оголошені з анотацією @Test. Перед кожним тестовим прикладом викликаються усі методи з анотацією @BeforeEach. Після кожного тестового прикладу викликаються усі методи з анотацією @AfterEach.

Останніми викликаються усі методи з анотацією @AfterAll.

 


Споріднені теми