- Тестирование в Java. JUnit
- JUnit 3
- Дополнительные возможности
- JUnit 4
- Основные аннотации
- Правила
- Запускалки
- Вывод
- Литература
- Saved searches
- Use saved searches to filter your results more quickly
- License
- robertsmieja/java-test-utils
- Name already in use
- Sign In Required
- Launching GitHub Desktop
- Launching GitHub Desktop
- Launching Xcode
- Launching Visual Studio Code
- Latest commit
- Git stats
- Files
- README.md
- About
Тестирование в Java. JUnit
Сегодня все большую популярность приобретает test-driven development(TDD), техника разработки ПО, при которой сначала пишется тест на определенный функционал, а затем пишется реализация этого функционала. На практике все, конечно же, не настолько идеально, но в результате код не только написан и протестирован, но тесты как бы неявно задают требования к функционалу, а также показывают пример использования этого функционала.
Итак, техника довольно понятна, но встает вопрос, что использовать для написания этих самых тестов? В этой и других статьях я хотел бы поделиться своим опытом в использовании различных инструментов и техник для тестирования кода в Java.
Ну и начну с, пожалуй, самого известного, а потому и самого используемого фреймворка для тестирования — JUnit. Используется он в двух вариантах JUnit 3 и JUnit 4. Рассмотрю обе версии, так как в старых проектах до сих пор используется 3-я, которая поддерживает Java 1.4.
Я не претендую на автора каких-либо оригинальных идей, и возможно многим все, о чем будет рассказано в статье, знакомо. Но если вам все еще интересно, то добро пожаловать под кат.
JUnit 3
Для создания теста нужно унаследовать тест-класс от TestCase, переопределить методы setUp и tearDown если надо, ну и самое главное — создать тестовые методы(должны начинаться с test). При запуске теста сначала создается экземляр тест-класса(для каждого теста в классе отдельный экземпляр класса), затем выполняется метод setUp, запускается сам тест, ну и в завершение выполняется метод tearDown. Если какой-либо из методов выбрасывает исключение, тест считается провалившимся.
Примечание: тестовые методы должны быть public void, могут быть static.
Сами тесты состоят из выполнения некоторого кода и проверок. Проверки чаще всего выполняются с помощью класса Assert хотя иногда используют ключевое слово assert.
Рассмотрим пример. Есть утилита для работы со строками, есть методы для проверки пустой строки и представления последовательности байт в виде 16-ричной строки:
public abstract class StringUtils < private static final int HI_BYTE_MASK = 0xf0; private static final int LOW_BYTE_MASK = 0x0f; private static final char[] HEX_SYMBOLS = < '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', >; public static boolean isEmpty(final CharSequence sequence) < return sequence == null || sequence.length() public static String toHexString(final byte[] data) < final StringBuffer builder = new StringBuffer(2 * data.length); for (byte item : data) < builder.append(HEX_SYMBOLS[(HI_BYTE_MASK & item) >>> 4]); builder.append(HEX_SYMBOLS[(LOW_BYTE_MASK & item)]); > return builder.toString(); > >
Напишем для нее тесты, используя JUnit 3. Удобнее всего, на мой взгляд, писать тесты, рассматривая нейкий класс как черный ящик, писать отдельный тест на каждый значимый метод в этом классе, для каждого набора входных параметров какой-то ожидаемый результат. Например, тест для isEmpty метода:
public class StringUtilsJUnit3Test extends TestCase < private final Map toHexStringData = new HashMap(); protected void setUp() throws Exception < toHexStringData.put("", new byte[0]); toHexStringData.put("01020d112d7f", new byte[] < 1, 2, 13, 17, 45, 127 >); toHexStringData.put("00fff21180", new byte[] < 0, -1, -14, 17, -128 >); //. > protected void tearDown() throws Exception < toHexStringData.clear(); >public void testToHexString() < for (Iterator iterator = toHexStringData.keySet().iterator(); iterator.hasNext();) < final String expected = (String) iterator.next(); final byte[] testData = (byte[]) toHexStringData.get(expected); final String actual = StringUtils.toHexString(testData); assertEquals(expected, actual); >> //. >
Дополнительные возможности
Кроме того, что было описано, есть еще несколько дополнительных возможностей. Например, можно группировать тесты. Для этого нужно использовать класс TestSuite:
public class StringUtilsJUnit3TestSuite extends TestSuite < public StringUtilsJUnit3TestSuite() < addTestSuite(StringUtilsJUnit3Test.class); addTestSuite(OtherTest1.class); addTestSuite(OtherTest2.class); >>
public class StringUtilsJUnit3RepeatedTest extends RepeatedTest < public StringUtilsJUnit3RepeatedTest() < super(new StringUtilsJUnit3Test(), 100); >>
public class StringUtilsJUnit3ExceptionTest extends ExceptionTestCase < public StringUtilsJUnit3ExceptionTest(final String name) < super(name, NullPointerException.class); >public void testToHexString() < StringUtils.toHexString(null); >>
Как видно из примеров все довольно просто, ничего лишнего, минимум нужный для тестирования(хотя недостает и некоторых нужных вещей).
JUnit 4
Здесь была добавлена поддержка новых возможностей из Java 5, тесты теперь могут быть объявлены с помощью аннотаций. При этом существует обратная совместимость с предыдущей версией фреймворка, практически все рассмотренные выше примеры будут работать и здесь(за исключением RepeatedTest, его нет в новой версии).
Основные аннотации
public class StringUtilsJUnit4Test extends Assert < private final MaptoHexStringData = new HashMap(); @Before public static void setUpToHexStringData() < toHexStringData.put("", new byte[0]); toHexStringData.put("01020d112d7f", new byte[] < 1, 2, 13, 17, 45, 127 >); toHexStringData.put("00fff21180", new byte[] < 0, -1, -14, 17, -128 >); //. > @After public static void tearDownToHexStringData() < toHexStringData.clear(); >@Test public void testToHexString() < for (Map.Entryentry : toHexStringData.entrySet()) < final byte[] testData = entry.getValue(); final String expected = entry.getKey(); final String actual = StringUtils.toHexString(testData); assertEquals(expected, actual); >> >
- Для упрощения работы я предпочитаю наследоваться от класса Assert, хотя это необязательно.
- Аннотация Before обозначает методы, которые будут вызваны до исполнения теста, методы должны быть public void. Здесь обычно размещаются предустановки для теста, в нашем случае это генерация тестовых данных (метод setUpToHexStringData).
- Аннотация @BeforeClass обозначает методы, которые будут вызваны до создания экземпляра тест-класса, методы должны быть public static void. Имеет смысл размещать предустановки для теста в случае, когда класс содержит несколько тестов использующих различные предустановки, либо когда несколько тестов используют одни и те же данные, чтобы не тратить время на их создание для каждого теста.
- Аннотация After обозначает методы, которые будут вызваны после выполнения теста, методы должны быть public void. Здесь размещаются операции освобождения ресурсов после теста, в нашем случае — очистка тестовых данных (метод tearDownToHexStringData).
- Аннотация @AfterClass связана по смыслу с @BeforeClass, но выполняет методы после теста, как и в случае с @BeforeClass, методы должны быть public static void.
- Аннотация Test обозначает тестовые методы. Как и ранее, эти методы должны быть public void. Здесь размещаются сами проверки. Кроме того, у данной аннотации есть два параметра, expected — задает ожидаемое исключение и timeout — задает время, по истечению которого тест считается провалившимся.
@Test(expected = NullPointerException.class) public void testToHexStringWrong() < StringUtils.toHexString(null); >@Test(timeout = 1000) public void infinity()
Если какой-либо тест по какой-либо серьезной причине нужно отключить(например, этот тест постоянно валится, но его исправление отложено до светлого будущего) его можно зааннотировать @Ignore. Также, если поместить эту аннотацию на класс, то все тесты в этом классе будут отключены.
@Ignore @Test(timeout = 1000) public void infinity()
Правила
Кроме всего вышеперечисленного есть довольно интересная вещь — правила. Правила это некое подобие утилит для тестов, которые добавляют функционал до и после выполнения теста.
Например, есть встроенные правила для задания таймаута для теста(Timeout), для задания ожидаемых исключений(ExpectedException), для работы с временными файлами(TemporaryFolder) и д.р. Для объявления правила необходимо создать public не static поле типа производного от MethodRule и зааннотировать его с помощью Rule.
public class OtherJUnit4Test < @Rule public final TemporaryFolder folder = new TemporaryFolder(); @Rule public final Timeout timeout = new Timeout(1000); @Rule public final ExpectedException thrown = ExpectedException.none(); @Ignore @Test public void anotherInfinity() < while (true); >@Test public void testFileWriting() throws IOException < final File log = folder.newFile("debug.log"); final FileWriter logWriter = new FileWriter(log); logWriter.append("Hello, "); logWriter.append("World. "); logWriter.flush(); logWriter.close(); >@Test public void testExpectedException() throws IOException < thrown.expect(NullPointerException.class); StringUtils.toHexString(null); >>
Также в сети можно найти и другие варианты использования. Например, здесь рассмотрена возможность параллельного запуска теста.
Запускалки
Но и на этом возможности фреймворка не заканчиваются. То, как запускается тест, тоже может быть сконфигурировано с помощью @RunWith. При этом класс, указанный в аннотации должен наследоваться от Runner. Рассмотрим запускалки, идущие в комплекте с самим фреймворком.
JUnit4 — запускалка по умолчанию, как понятно из названия, предназначена для запуска JUnit 4 тестов.
JUnit38ClassRunner предназначен для запуска тестов, написанных с использованием JUnit 3.
SuiteMethod либо AllTests тоже предназначены для запуска JUnit 3 тестов. В отличие от предыдущей запускалки, в эту передается класс со статическим методом suite возвращающим тест(последовательность всех тестов).
Suite — эквивалент предыдущего, только для JUnit 4 тестов. Для настройки запускаемых тестов используется аннотация @SuiteClasses.
@Suite.SuiteClasses( < OtherJUnit4Test.class, StringUtilsJUnit4Test.class >) @RunWith(Suite.class) public class JUnit4TestSuite
Enclosed — то же, что и предыдущий вариант, но вместо настройки с помощью аннотации используются все внутренние классы.
Categories — попытка организовать тесты в категории(группы). Для этого тестам задается категория с помощью @Category, затем настраиваются запускаемые категории тестов в сюите. Это может выглядеть так:
public class StringUtilsJUnit4CategoriesTest extends Assert < //. @Category(Unit.class) @Test public void testIsEmpty() < //. >//. > @RunWith(Categories.class) @Categories.IncludeCategory(Unit.class) @Suite.SuiteClasses( < OtherJUnit4Test.class, StringUtilsJUnit4CategoriesTest.class >) public class JUnit4TestSuite
Parameterized — довольно интересная запускалка, позволяет писать параметризированные тесты. Для этого в тест-классе объявляется статический метод возвращающий список данных, которые затем будут использованы в качестве аргументов конструктора класса.
@RunWith(Parameterized.class) public class StringUtilsJUnit4ParameterizedTest extends Assert < private final CharSequence testData; private final boolean expected; public StringUtilsJUnit4ParameterizedTest(final CharSequence testData, final boolean expected) < this.testData = testData; this.expected = expected; >@Test public void testIsEmpty() < final boolean actual = StringUtils.isEmpty(testData); assertEquals(expected, actual); >@Parameterized.Parameters public static List
Theories — чем-то схожа с предыдущей, но параметризирует тестовый метод, а не конструктор. Данные помечаются с помощью @DataPoints и @DataPoint, тестовый метод — с помощью Theory. Тест использующий этот функционал будет выглядеть примерно так:
@RunWith(Theories.class) public class StringUtilsJUnit4TheoryTest extends Assert < @DataPoints public static Object[][] isEmptyData = new Object[][] < < "", true >, < " ", false >, < "some string", false >, >; @DataPoint public static Object[] nullData = new Object[] < null, true >; @Theory public void testEmpty(final Object. testData) < final boolean actual = StringUtils.isEmpty((CharSequence) testData[0]); assertEquals(testData[1], actual); >>
Как и в случае с правилами, в сети можно найти и другие варианты использования. Например, здесь рассмотрена та же возможность паралельного запуска теста, но с использованием запускалок.
Вывод
Это, конечно же, не все, что можно было сказать по JUnit-у, но я старался вкратце и по делу. Как видно, фреймворк достаточно прост в использовании, дополнительных возможностей немного, но есть возможность расширения с помощью правил и запускалок. Но несмотря на все это я все же предпочитаю TestNG с его мощным функционалом, о котором и расскажу в следующей статье.
Литература
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
Java utility classes useful for unit testing
License
robertsmieja/java-test-utils
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
This project is intended to serve as a set of common utilities used in testing, between my personal project.
It currently consists of a set of utilities to simplify unit testing of all getters/setters, equals(), hashCode(), and toString() methods.
- Java 1.8 or newer
- Apache Commons java.lang3 at runtime
- Apache Commons Collections 4 runtime
- JUnit 5 API at compile time
The library is currently available on JCenter at the following repository.
There is no current stable release yet, but I’ve published a «milestone» release that should be relatively stable and usable. See this unit test as an example of how to use the current implementation.
The library is compatible with JUnit 4 projects. See this example JUnit 4 Java project.
- Java 1.8 or newer
- JUnit 5.0.0-M4 or newer
- IDE support for JUnit 5 (Currently only IntelliJ IDEA)
If there are any bugs, or ideas on how to improve these library, please open a new GitHub issue.
The code is licensed under Apache 2.0. If you find it useful, I’d love to know.
About
Java utility classes useful for unit testing