Как писать тесты на java
Перейти к содержимому

Как писать тесты на java

  • автор:

Unit тесты в Java. Краткое руководство

Платон Цыбульский

Unit тесты в Java. Краткое руководство

Каждая выполненная задача в программировании требует тестирования, потому что от ошибок, как известно, никто не застрахован.

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

А что если ваше приложение большое и в нем много зависящих друг от друга модулей, классов и компонент, и ваша задача — изменить поведение существующего кода, при этом не повредив старое?

Затем представим, для простоты понимания, что у нас есть класс калькулятор, который может прибавлять и вычитать:

Unit тест для такого класса будет выглядеть так:

  • Размещаться данный класс должен в папке test, которая специально создана для хранения тестовых классов.
  • Название класса должно соответствовать одной из масок: *Test, Test*, *TestCase для того, чтобы maven surefire plugin смог найти ваш тестовый класс.
  • Методы должны иметь возвращаемый тип void в сигнатуре и аннотацию @Test, которая определяет, что метод является тестовым.
  • Аннотация @Beforeуказываетна то, что метод будет выполняться перед каждым тестируемым методом.

Также для проверки результата используется специальный проверяемый метод assertEquals, который в нашем случае может принимать сообщение, которое будет показываться при несоответствии фактического и ожидаемого результата, затем второй параметр – фактический результат и третий ожидаемый результат.

Далее открываем терминал, перейдем в папку с нашим проектом и выполним mvn test:

По выводу лога консоли видно, что тесты прошли успешно. Если же мы изменим знак в методе add c + на *, то тест будет failed, и лог будет выглядеть так:

Таким образом, мы сразу видим, какой тест у нас не прошел проверку, и можем начать отладку с нужной точки.

Рекомендуем курс по теме

В следующей таблице приведен обзор имеющихся в аннотации JUnit 4.x.

Аннотация Описание
@Test public void method() public void method() public void method() public static void method() public static void method() Аннотация @Test определяет что метод method() является тестовым.
@Before Аннотация @Before указывает на то, что метод будет выполнятся перед каждым тестируемым методом @Test.
@After Аннотация @After указываетна то что метод будет выполнятся после каждого тестируемого метода @Test
@BeforeClass Аннотация @BeforeClass указывает на то, что метод будет выполнятся в начале всех тестов, а точней в момент запуска тестов(перед всеми тестами @Test).
@AfterClass Аннотация @AfterClass указывает на то, что метод будет выполнятся после всех тестов.
@Ignore Аннотация @Ignore говорит, что метод будет проигнорирован в момент проведения тестирования.
@Test (expected = Exception.class) (expected = Exception.class) — указывает на то, что в данном тестовом методе вы преднамеренно ожидается Exception.
@Test (timeout=1000) (timeout=1000) — указывает, что тестируемый метод не должен занимать больше чем 1 секунду.

Проверяемые методы (основные)

Метод Описание
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) Проверяет, что обе переменные относятся к разным объектам.

Также я прикрепил пример с ООП моделью компании, в которой подсчитываются затраты на зарплату для сотрудников по компании и департаменту.

Основные методы покрыты unit тестами, код размещен тут.

Как правильно писать unit тесты java

Для написания тестов на Java с использованием фреймворка JUnit и автоматической сборки с помощью Gradle , необходимо выполнить несколько шагов:

  • Создать проект Gradle в IDE (например, в IntelliJ IDEA ).
  • Добавить зависимость JUnit в файл build.gradle :
  • Создать тестовый класс, который будет содержать методы с аннотацией @Test :
import org.junit.Test; import static org.junit.Assert.*; public class MyTest  @Test public void testSum()  assertEquals(4, 2 + 2); > @Test public void testMax()  int[] numbers = 1, 2, 3, 4, 5>; int max = numbers[0]; for (int i = 1; i  numbers.length; i++)  if (numbers[i] > max)  max = numbers[i]; > > assertEquals(5, max); > > 
  • Запустить тесты с помощью команды gradle test в терминале или из IDE.

После выполнения этих шагов, Gradle автоматически соберет и запустит все тесты, которые находятся в пакете src/test/java . Результаты тестирования будут отображены в консоли или в специальной вкладке в IDE, и вы сможете увидеть, прошли ли все тесты успешно или есть ошибки.

08 апреля 2023

Для написания теста на Java необходимо использовать какой-либо фреймворк для тестирования, например, JUnit . Рассмотрим пример написания теста на JUnit.

  • Подключите зависимость JUnit в свой проект. Если вы используете Maven , добавьте следующий код в pom.xml :
junit junit 4.13.2 test  
  • Создайте класс для тестирования. Например, если вы хотите протестировать класс Calculator , создайте класс CalculatorTest :
import org.junit.Test; import static org.junit.Assert.*; public class CalculatorTest  @Test public void testAddition()  Calculator calculator = new Calculator(); int result = calculator.add(2, 3); assertEquals(5, result); > @Test public void testSubtraction()  Calculator calculator = new Calculator(); int result = calculator.subtract(5, 3); assertEquals(2, result); > > 
  • Создайте методы тестирования. Каждый метод должен быть аннотирован аннотацией @Test . Внутри метода вызывайте методы, которые вы хотите протестировать, и сравнивайте ожидаемый результат с фактическим. В этом примере мы тестируем методы add и subtract класса Calculator
  • Запустите тесты. В большинстве сред разработки можно запустить тесты, нажав на кнопку «запустить тесты». Вы также можете запустить тесты из командной строки с помощью команды mvn test (если вы используете Maven ) или java -cp org.junit.runner.JUnitCore

Это только основы, но они помогут вам начать написание юнит-тестов в Java

Причины тестирования — Java: Автоматическое тестирование

Какую главную задачу должны решать тесты? Этот вопрос невероятно важен. Ответ на него даёт понимание того, как правильно писать тесты и как писать их не нужно.

Представьте, что вы написали метод StringUtils.capitalize(text) , который делает заглавной первую букву переданной строки:

StringUtils.capitalize("hello"); // "Hello" 

Вот один из вариантов его реализации:

class StringUtils  public static String capitalize(String text)  return text.substring(0, 1).toUpperCase() + text.substring(1); > > 

Что мы делаем после написания метода? Проверяем, как он работает. Например, пишем простейшую программу и пытаемся вызвать этот метод с различными аргументами:

System.out.println(StringUtils.capitalize("hello")); // => "Hello" System.out.println(StringUtils.capitalize("how are you")); // => "How are you" 

Таким нехитрым способом убеждаемся, что метод работает. По крайней мере для тех аргументов, которые мы передали в него. Если во время проверки заметили ошибки, то исправляем метод и повторяем всё заново.

Фактически, весь этот процесс и есть тестирование. Но не автоматическое, а ручное. Задача такого тестирования — убедиться, что код работает как надо. И нам совершенно без разницы, как конкретно реализован этот метод. Это и есть главный ответ на вопрос, заданный в начале урока.

Тесты проверяют, что код (или приложение) работает корректно. И не заботятся о том, как конкретно написан код, который они проверяют.

Автоматические тесты

Всё, что требуется от автоматических тестов — повторить проверки, которые мы выполняли, делая ручное тестирование. Для этого достаточно старого доброго if и исключений.

Даже если вы не знакомы с исключениями, ничего страшного. В этом курсе достаточно знать две вещи: для чего они нам нужны и какой у них синтаксис. До сих пор в курсах Хекслета вы встречались с ошибками, которые возникают непроизвольно: вызов несуществующего метода, обращение к несуществующей константе и так далее. Но ошибки можно порождать самостоятельно с помощью исключений, что необходимо для нашей ситуации. В Java есть специальный тип исключений, который принято «пробрасывать» при возникновении ошибок в тестах. Они называются AssertionError , что в дословном переводе означает «ошибка утверждения», т.е. вы в тестах что-то утверждали и, если это утверждение оказалось ошибочным, то пробрасывается AssertionError . Исключения создаются такой конструкцией:

// Дословно: выбросить новую ошибку // Исключения бросают throw new AssertionError("описание исключения"); // Код, следующий за этим выражением, не выполнится, а сама программа завершится с ошибкой System.out.println("nothing"); 
class StringUtilsTest  public static void testCapitalize()  // Если результат метода не равен ожидаемому значению if (!"Hello".equals(StringUtils.capitalize("hello")))  // Выбрасываем исключение и завершаем выполнение теста throw new AssertionError("Метод работает неверно!"); > > > 

Теперь для запуска теста вам достаточно лишь вызвать метод testCapitalize() в вашем методе main и выполнить программу.

Из примера выше видно, что тесты — это точно такой же код, как и любой другой. Он подчиняется тем же правилам, например, стандартам кодирования. А ещё он может содержать ошибки. Но это не значит, что надо писать тесты на тесты. Избежать всех ошибок невозможно, да и не нужно, иначе стоимость разработки стала бы неоправданно высокой. Обнаруженные ошибки в тестах исправляются, и жизнь продолжается дальше 😉

В коде, тесты складывают в директорию src/test:

Структура этой директории, обычно, повторяет структуру исходного кода.

Как пишутся тесты

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

Если поменялся контракт (входные данные или выход), то придётся переписывать тесты. Если контракт остался тем же, но поменялись внутренности метода, то тесты должны продолжать работать без изменений.

class StringUtils  // Пример другой реализации того же самого метода public static String capitalize(String text)  return Character.toUpperCase(text.charAt(0)) + text.substring(1); > > 

Хорошие тесты ничего не знают про внутреннее устройство проверяемого кода. Это делает их более универсальными и надёжными.

Сколько и какие нужно писать проверки?

Невозможно написать тесты, которые гарантируют 100% работоспособность кода. Для этого потребовалось бы реализовать проверки всех возможных аргументов, что физически неосуществимо. С другой стороны, без тестов вообще нет никаких гарантий, только честное слово разработчиков.

При написании тестов нужно ориентироваться на разнообразие входных данных. У любого метода есть один или несколько основных сценариев использования. Например, в случае capitalize — это любое слово. Достаточно написать ровно одну проверку, которая покрывает этот сценарий. Дальше нужно смотреть на «пограничные случаи». Это ситуации, в которых код может повести себя по-особенному:

  • Работа с пустой строкой
  • Обработка null
  • Деление на ноль (в большинстве языков вызывает ошибку)
  • Специфические ситуации для конкретных алгоритмов

Для capitalize пограничным случаем будет пустая строка:

if (!"".equals(StringUtils.capitalize("")))  throw new AssertionError("Метод работает неверно!"); > 

Добавив тест на пустую строку, мы увидим, что вызов показанного в начале урока метода capitalize завершается с ошибкой. Внутри него идёт обращение к первому индексу строки без проверки его существования. Исправленная версия кода:

public static String capitalize(String text)  if ("".equals(text))  return ""; > return text.substring(0, 1).toUpperCase() + text.substring(1); > 

В большом числе ситуаций пограничные случаи требуют отдельной обработки, наличия условных конструкций. Тесты должны быть построены таким образом, чтобы они затрагивали каждую такую конструкцию. Но не забывайте, что условные конструкции могут порождать хитрые связи. Например, два независимых условных блока порождают 4 возможных сценария:

  • Метод выполнился так, что не был выполнен ни один условный блок
  • Метод выполнился так, что был выполнен только первый условный блок
  • Метод выполнился так, что был выполнен только второй условный блок
  • Метод выполнился так, что были выполнены оба условных блока

Комбинация всех возможных вариантов поведения метода называется цикломатической сложностью. Это число показывает все возможные пути кода внутри метода. Цикломатическая сложность — хороший ориентир для понимания того, сколько и какие тесты нужно написать.

Иногда пограничные случаи не связаны с условными конструкциями. Особенно часто такие ситуации встречаются там, где есть вычисления границ слов или массивов. Такой код может работать в большинстве ситуаций, но только в некоторых может давать сбой:

// В этом методе забыли проверить что входной аргумент не равен null // Этот код сработает корректно в большинстве ситуаций, // Но если text == null, то программа упадёт с NullPointerException public static int len(String text)  return text.length(); > 

Собирая всё вместе

Содержимое класса с тестами:

package io.hexlet; public class StringUtilsTest  public static void testCapitalize()  if (!"Hello".equals(StringUtils.capitalize("hello")))  throw new AssertionError("Метод работает неверно!"); > if (!"".equals(StringUtils.capitalize("")))  throw new AssertionError("Метод работает неверно!"); > System.out.println("Все тесты пройдены!"); > > 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Unit Test

Unit Testing

Модульное тестирование, или юнит-тестирование (unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы.

Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок.

Общий раздел по Unit Testing
Пример создания Unit-тестов
  • JUnit — введение в юнит-тесты. Пример JUnit Hello world
  • JUnit — Suite тест. Запуск нескольких тестов с помощью аннотаций @Suite и @RunWith
  • Параметризированные тесты в JUnit. Использование @Parameterized.Parameters
  • JUnit Listener — добавление слушателей в юнит-тесты

Share Button

13162 Total Views 3 Views Today
Views: 11 864

Добавить комментарий Отменить ответ

Для отправки комментария вам необходимо авторизоваться.

Подписывайтесь

Нашли ошибку?

Выделите и нажмите CTRL+ENTER 🙂

Copyright © JavaStudy — руководство для java разработчика All Rights Reserved.

Powered by WordPress & Lightning Theme by Vektor,Inc. technology.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *