JUnit – фреймворк для модульного тестирования Java программ, принадлежащий семейству фреймворков xUnit для различных языков программирования (например: CPPUnit – C++, JSUnit – JavaScript, NUnit — C#, PHPUnit — PHP). Основная идея модульного тестирования заключается в проверке корректности работы отдельных модулей исходного кода программы, в условиях изолированности тестируемого модуля от других. JUnit играет важнейшую роль в технике разработки через тестирование (test-driven development – TDD), которая заключается в повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, и только затем пишется код, который позволит пройти этот тест.
Будет рассматриваться API версии 4 и выше, которая значительно отличается от версии 3.8. В обзоре раскрываются основы написания простейших JUnit тестов, приводится типовой набор утверждений (assert-ов) и правила определения фикстур (fixtures). Умышлено не приводятся описание работы с runner-ом, параметризованного тесстирования, ruler`s, различных подходов к работе с исключениями и другие возможности (так сказать вариант для чайников).
Для реализации Unit тестов организуется отдельный класс, который содержит сами тестовые методы (по правилам JUnit они должны быть public void, имена — произвольны), так же в этом классе могут содержаться обычные и вспомогательные методы. В более ранних версиях тестирующий класс должен был быть наследником junit.framework.TestCase, а все тестовые методы должны были начинаться с «test» (Например testSum), теперь все это заменяется аннотациями. Чтобы runner тестов мог определить, что это тестовый метод, его необходимо пометить аннотацией @Test. У аннотации @Test могут быть представлены следующие параметры (по отдельности или через запятую):
- expected – указывает на появление исключения Exception. Если же исключение не будет выброшено, то тест провалится.
@Test(expected = ArithmeticException.class)
- timeout – определяет предельное время в милисикундах отведенное на выполнение теста, при превышении которого засчитать тест проваленым.
@Test(timeout = 1000)
В случае необходимости пропустить какой либо тест, нужно либо просто убрать аннотацию @Test перед тестовым методом, либо добавить перед ней аннотацию @Ignore. При этом в качестве параметра можно указать причину пропуска теста. Пример:
@Ignore(“Тест ещё не реализован!!!”)
@Test
public void testFunct() {
// ???
}
По ссылке можно ознакомиться со способом организовать пропуск тестов при выполнении определенного условия основанный на применении «предположений» (assumption).
Тестовые методы строятся вокруг проверки утверждений (assert), представляющих собой статические методы определенные в классе Assert. Полный список доступных утверждений можно посмотреть по ссылке — https://junit.sourceforge.net/javadoc/org/junit/Assert.html. Каждому утверждению можно добавить первым параметром строку, которая будет выводиться в случае провала теста. Ниже представлен основной набор из наиболее часто используемых assert-ов.
fail([message])
— утверждение того, что тест терпит неудачу. Может использоваться для завершения теста и вывода причины провала в случае обнаружения некорректной работы в процессе тестирования.assertEquals([message], expected, actual)
– утверждение эквивалентности. Сравнивает ожидаемый и полученный результат. При сравнении примитивных типов проверяется равенство значений. Для сравнения объектов используется метод equals(), если он определен. Если же он не определен – работает как assertSame. (Для массивов не подходит, т.к. сравнивает просто ссылки!).assertArrayEquals([message], expected, actual)
– поэлементно сравнивает массивы.assertSame([message], expected, actual)
– просто сравнивает объекты при помощи оператора ==, то есть проверяет, являются ли параметры ссылками на один и тот же объект.assertNotSame([message], expected, actual)
– утверждение обратное assertSame.assertTrue ([message], boolean condition)
– булево утверждение. Проверяет на истинность логическое условие.assertFalse ([message], boolean condition)
– булево утверждение. Проверяет на истинность логическое условие.assertNull([message], object)
– Null утверждение. Проверяет содержимое объектной переменной на Null значение.assertNotNull([message], object)
– утверждение обратное assertNull.assertThat([String message], T actual, Matcher<T> matcher)
— Проверка, что actual удовлетворяет условию заданному matcher.
Пример записи теста:
import static org.junit.Assert.*;
import org.junit.Test;
public class test {
@Test
public void multiplicationOfZeroShouldReturnZero() {
MyClass tester = new MyClass(); //класс содержащий статичный метод multiply(double, double)
//тест будет провален при невыполнении любого из трех следующих утверждений
assertEquals("1 x 0 должно быть 0", 0, tester.multiply(1, 0), 0.00001);
assertEquals("0 x 1 должно быть 0", 0, tester.multiply(0, 1), 0.00001);
assertEquals("0 x 0 должно быть 0", 0, tester.multiply(0, 0), 0.00001);
}
@Test
public void someNewTest(){
fail("Тест ещё не реализован"); //автоматический провал теста
}
}
Подразумевается наличие тестируемого класса MyClass в котором определен статический метод multiply(double a, double b), возвращающий результат перемножения a и b. Четвертый параметр в assertEquals задает точность сравнения вещественных чисел.
Фикстура (англ. fixtures) – заранее подготовленное состояние окружения тестирования, требуемое для гарантии повторяемости процесса. Простыми словами это начальное состояние среды перед запуском тестовых методов (набор экземпляров классов, определенное состояние БД, наличиенеобходимых файлов и т.д.). Для задания фикстуры и высвобождения зарезирвированных ресурсов после выполнения теста применяются методы помеченные аннотациями @Before и @After. Метод, помеченный аннотацией @Before будет выполняться перед каждым тестовым случаем, а помеченный @After — после каждого тестового случая. Бывают случаи, когда необходимо провести инициализацию и высвобождение ресурсов всего один раз (до и после выполнения всех тестов). Для этого можно применить соответствующие аннотации – @BeforeClass и @AfterClass.
Порядок вызова методов проиллюстрирован следующим примером:
import org.junit.*;
public class BasicTest {
@BeforeClass
public static void runOnceBeforeClass() {
System.out.println("@BeforeClass - Выполняется один раз в самом начале");
}
@AfterClass
public static void runOnceAfterClass() {
System.out.println("@AfterClass - Выполняется один раз в самом конце");
}
@Before
public void runBeforeTestMethod() {
System.out.println("@Before - выполняется перед каждым тестом");
}
@After
public void runAfterTestMethod() {
System.out.println("@After - выполняется после каждого теста");
}
@Test
public void test_1() {
System.out.println("@Test - tes_1");
}
@Test
public void test_2() {
System.out.println("@Test - test_2");
}
}
Результат выполнения:
@BeforeClass - Выполняется один раз в самом начале
@Before - выполняется перед каждым тестом
@Test - tes_1
@After - выполняется после каждого теста
@Before - выполняется перед каждым тестом
@Test - tes_2
@After - выполняется после каждого теста
@AfterClass - Выполняется один раз в самом конце
Стоит помнить, что каждый тест выполняется отдельно от других в произвольном порядке (хотя можно настроить порядок выполнения). Поэтому, в общем случае, тесты не должны зависеть друг от друга.
В современных IDE нет никакого труда запустить тесты, причем можно даже ограничиться только одним тестовым случаем, просто выделяя его перед запуском. Для ручного же запуска тестов программным кодом, можно воспользоваться Runner‘ом, либо текстовым – junit.textui.TestRunner, либо графическим – junit.swingui.TestRunner, junit.awtui.TestRunner. В таком случае Runner можно настроить по собственному желанию.