12 инструментов для интеграционных и unit-тестов в Java


Оглавление (нажмите, чтобы открыть):

Тестирование Java кода с помощью JUnit — Туториал

English version of this article you can find here.

Тестирование далеко не всегда бывает веселым и интересным. Этот процесс обычно достаточно продолжителен и порой полон монотонной работы. Кажется, еще совсем недавно программисты пользовались стандартным выводом или же дебаггером для тестирования java классов.

В этой статье я опишу библиотеку JUnit 4, которая во многом упрощает и автоматизирует процесс написания тестов.

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

Теперь напишем Unit тесты. Для этого создадим класс с некоторым количеством тестовых методов. Естественно, класс может содержать и обычные вспомогательные методы. Чтобы runner тестов мог определить, кто есть кто, тестовые методы необходимо помечать аннтоацией @Test.

У аннотации могут быть проставлены такие параметры:

  • expected — указываем какое исключение будет сгенерировано методом (см. пример ниже);
  • timeout — через какое время в милисекундах прекратить выполнение теста и засчитать его как неуспешный.

Если вы хотите указать, что определенный тест необхдимо пропустить, то пометьте его аннотацией @Ignore. Хотя можно просто удалить аннотацию @Test.

Бывает такое, что для выполнения каждого тестового сценария вам необходим некоторый контекст, например, заранее созданные экземпляры классов. А после выполнения нужно освободить зарезервированные ресурсы. В этом случае вам понадобятся аннтоации @Before и @After. Метод, помеченный @Before будет выполняться перед каждым тестовым случаем, а метод, помеченный @After — после каждого тестового случая.

Если же инициализацию и освобождение ресурсов нужно сделать всего один раз — соответственно до и после всех тестов — то используйте пару аннотаций @BeforeClass и @AfterClass.

А вот и сам тестовый класс с несколькими тестовыми сценариями:

Метод calls тестирует правильность счетчика вызовов. Метод factorial проверяет правильность вычисления факториала для некоторых стандартных значений. Метод factorialNegative проверяет, что для отрицательных значений факотриала будет брошен IllegalArgumentException. Метод todo будет проигнорирован. Попробуйте убрать аннотацию @Ignore, когда будете экспериментировать с кодом.

Метод assertTrue проверяет, является ли результат выражения верным. Некоторые другие методы, которые могут пригодиться:

  • assertEquals — ожидаемый результат и полученный результат совпадают;
  • assertNull — результатом выражения есть null;
  • assertNotNull — результат выражения отличен от null;
  • assertSame — ожидаемый и полученный объекты это один и тот же объект.
  • fail — метод генерирует исключение AssertionError — добавляем туда, куда не должен дойти ход выполнения программы.

В нашем современном мире IDE умеют находить и просто запускать тесты в проекте. Но что делать, если вы хотите запустить их вручную с помощью программного кода. Для этого можно воспользоваться Runner’ом. Бывают текстовый — junit.textui.TestRunner, графические версии — junit.swingui.TestRunner, junit.awtui.TestRunner.

Но чуть более современный метод — это использование класса JUnitCore. Добавьте следующий метод main в класс MathFuncTest:

И результат выполнения:

В более ранних версиях JUnit для написания тестового класса нужно было создать наследника junit.framework.TestCase. Затем необходимо было определить конструктор, принимающий в качестве параметра String — название метода — и передать его родительскому классу. Каждый тестовый метод обязан был начинаться с префикса test. Для инициализации и освобождения ресурсов использовались методы setUp и tearDown. Короче ужас. Ну а сейчас все просто, да.

Вот и все на сегодня. Уверен, JUnit Framework во многом поможет вам. Комментарии и вопросы по поводу статьи приветствуются.

Тестирование программы, JUnit

JUnit — библиотека для модульного тестирования программ Java. Созданный Кентом Беком и Эриком Гаммой, JUnit принадлежит семье фреймворков xUnit для разных языков программирования, берущей начало в SUnit Кента Бека для Smalltalk. JUnit породил экосистему расширений — JMock, EasyMock, DbUnit, HttpUnit и т. д.

Библиотека JUnit была портирована на другие языки, включая PHP (PHPUnit), C# (NUnit), Python (PyUnit), Fortran (fUnit), Delphi (DUnit), Free Pascal (FPCUnit), Perl (Test::Unit), C++ (CPPUnit), Flex (FlexUnit), JavaScript (JSUnit).

JUnit – это Java фреймворк для тестирования, т. е. тестирования отдельных участков кода, например, методов или классов. Опыт, полученный при работе с JUnit, важен в разработке концепций тестирования программного обеспечения.

Пример теста JUnit

Необходимость использования JUnit

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

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


Виды тестирования и место JUnit тестирования в классификации

Тестирование программного обеспечение можно разделить на два вида:

  • тестирование черного ящика;
  • тестирование белого ящика.

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

  • юнит тесты — тестирование отдельных участков кода;
  • интеграционное тестирование — тестирование взаимодействия и совместной работы компонентов;
  • системное тестирование — тестирование всей системы как целого;
  • приемное тестирование — итоговое тестирование готовой системы на соответствие требованиям.

Юнит тестирование по определению является тестированием белого ящика.

Используется unit тестирование в двух вариантах — JUnit 3 и JUnit 4. Рассмотрим обе версии, так как в старых проектах до сих пор используется 3-я версия, которая поддерживает Java 1.4.

JUnit 3

Для создания теста следует наследовать тест-класс TestCase, переопределить методы setUp и tearDown при необходимости, ну и самое главное — разработать тестовые методы, наименование которых должно начинаться с аббривиатуры «test». При запуске теста сначала создается экземляр тест-класса (для каждого теста в классе отдельный экземпляр класса), затем выполняется метод setUp, запускается сам тест, ну и в завершение выполняется метод tearDown. Если какой-либо из методов вызывает исключение, тест считается провалившимся.

Примечание : тестовые методы должны быть public void, могут быть static.

Тесты состоят из выполнения некоторого кода и проверок. Проверки чаще всего выполняются с помощью класса Assert хотя иногда используют ключевое слово assert.

В качестве примера рассмотрим утилиту для работы со строками, включающую методы для проверки пустой строки и представления последовательности байт в виде 16-ричной строки:

Дополнительные возможности, TestSuite

JUnit 3 имеет несколько дополнительных возможностей. Например, можно группировать тесты. Для этого необходимо использовать класс TestSuite:

Можно исполнение теста повторить несколько раз. Для этого используется RepeatedTest :

Наследуя тест-класс от ExceptionTestCase, можно проверить код на выброс исключения :

Как видно из примеров все довольно просто и ничего лишнего — минимум кода для JUnit тестирования.

JUnit 4

В JUnit 4 добавлена поддержка новых возможностей из Java 5.0; тесты могут быть объявлены с помощью аннотаций. При этом существует обратная совместимость с предыдущей версией фреймворка. Практически все рассмотренные выше примеры будут работать и в JUnit 4 за исключением RepeatedTest, который отсутствует в новой версии.

Какие внесены изменения появились в JUnit 4? Рассмотрим тот же пример, но уже с использованием новых возможностей :

Что изменилось в JUnit 4 ?

  • Для упрощения работы можно наследоваться от класса Assert, хотя это необязательно.
  • Аннотация @Before обозначает методы, которые будут вызваны перед исполнением тестов. Методы должны быть public vo >

Игнорирование выполнения теста, JUnit Ignore

Если один из тестов по какой-либо серьезной причине необходимо отключить, например, тест постоянно завершается с ошибкой. Исправление теста можно отложить до светлого будущего аннотированием @Ignore. Если поместить эту аннотацию на класс, то все тесты в этом классе будут отключены.

Правила тастирования, JUnit Rule

JUnit позволяет использовать определенные разработчиком правила до и после выполнения теста, которые расширяют функционал. Например, есть встроенные правила для задания таймаута для теста (Timeout), для задания ожидаемых исключений (ExpectedException), для работы с временными файлами(TemporaryFolder) и др.

Для объявления правила необходимо создать public не static поле типа производного от MethodRule и аннотировать его с помощью ключевого слова Rule.


Наборы тестов, JUnit Suite, SuiteClasses

Запуск теста может быть сконфигурирован с помощью аннотации @RunWith. Тестовые классы, которые содержат в себе тестовые методы, можно объединить в наборы тестов (Suite). Например, создано два класса тестирования объектов : TestFilter, TestConnect. Эти два тестовых класса можно объединить в один тестовый класс TestWidgets.java :

Для настройки запускаемых тестов используется аннотация @SuiteClasses, в которую включены тестовые классы.

Аннотация Categories

Аннотация Categories позволяет объединить тесты в категории (группы). Для этого в тесте определяется категория @Category, после чего настраиваются запускаемые категории тестов в Suite. Это может выглядеть следующим образом:

Аннотация, JUnit Parameterized

Аннотация Parameterized позволяет использовать параметризированные тесты. Для этого в тест-классе объявляется статический метод, возвращающий список данных, которые будут использованы в качестве аргументов конструктора класса.

Параметризирование метода : Theories.class, DataPoints, DataPoint, Theory

Аннотация Theories параметризирует тестовый метод, а не конструктор. Данные помечаются с помощью @DataPoints и @DataPoint, тестовый метод — с помощью @Theory. Тест, использующий этот функционал, может выглядеть примерно следующим образом :

Порядок выполнения тестов

Если необходимо выполнить тест в определенном порядке, то можно воспользоваться аннотацией @FixMethodOrder(MethodSorters.NAME_ASCENDING), определенной в JUnit 4.11. Например :

В противном случае можно использовать следующие 2 подхода.

Список основных аннотаций

Аннотация Описание
@Test
public void testMethod()
метод является тестовым
@Test(timeout=100)
public void testMethod()
если время выполнения превысит параметр timeout, то тест будет завершен неудачно
@Test (expected = MyException.class)
public void testMethod()
метод должен выбросить исключение принадлежащие к классу MyException, в противном случае тест будет завершен неудачно
@Ignore
public void testMethod()
игнорировать тестовый метод
@BeforeClass
public static void testMethod()
метод вызывающийся один раз для класса перед выполнением тестовых методов; здесь можно разместить инициализацию которую нужно выполнять только один раз, например, прочитать данные, которые будут использоваться в тестовых методах или создать соединение с базой данных
@AfterClass
public static void testMethod()
метод вызывающийся один раз для класса после выполнения тестовых методов; здесь можно разместить деинициализацию которую нужно выполнять только один раз, например, закрыть соединение с базой данных или удалить данные, которые больше не нужны
@Before
public static void beforeMethod()
метод, вызывающийся перед каждым тестовым методом в тестовом классе; здесь можно выполнить необходимую инициализацию, например, выставить начальные параметры
@After
public static void afterMethod()
метод, вызывающийся после каждого тестового метода в тестовом классе; здесь можно выполнить необходимую деинициализацию, например, удалить данные, которые больше не нужны

Список типов проверок Asserts

Тип проверки Описание
fail()
fail(String message)
прерывание теста с ошибкой, т.е. тест будет неудачным
assertTrue(boolean condition)
assertTrue(java.lang.String message, boolean condition)
проверка на равенство условия condition значению true
assertFalse(boolean condition)
assertFalse(String message, boolean condition)
проверка на равенство условия condition значению false
assertEquals( expected, actual)
assertEquals(String message, expected, actual)
проверка на равенство; — это Object, int, double и т.д.
assertArrayEquals(byte[] expecteds, byte[] actuals)
assertArrayEquals(String message, [] expecteds, [] actuals)
проверка массивов на равенство; аналогично assertEquals; — это Object, int, double и т.д.
assertNotNull(Object object)
assertNotNull(String message, Object object)
проверка, что Object не null
assertNull(Object object)
assertNull(String message, Object object)
проверка, что Object null
assertSame(Object expected, Object actual)
assertSame(String message, Object expected, Object actual)
проверка на равенство двух объектов expected и actual, т.е. один и тот же объект

Пример JUnit тестирования

Для демонстрации основных возможностей JUnit, используем примитивный java класс FuncMath, который имеет два метода — нахождение факториала неотрицательного числа и суммы двух чисел. Кроме того, в экземпляре класса будет находится счетчик вызовов методов.

Иногда для выполнения каждого тестового сценария необходим некоторый контекст, например, заранее созданные экземпляры классов. А после выполнения нужно освободить зарезервированные ресурсы. В этом случае используют аннтоации @Before и @After. Метод, помеченный @Before будет выполняться перед каждым тестовым случаем, а метод, помеченный @After — после каждого тестового случая. Если же инициализацию и освобождение ресурсов нужно сделать всего один раз — соответственно до и после всех тестов — то используют пару аннотаций @BeforeClass и @AfterClass.

Тестовый класс с несколькими сценариями будет иметь следующий вид :

Метод calls тестирует правильность счетчика вызовов. Метод factorial проверяет правильность вычисления факториала для некоторых стандартных значений. Метод factorialNegative проверяет, что для отрицательных значений факотриала будет брошен IllegalArgumentException. Метод todo будет проигнорирован.

В заключении следует отметить, что в статье представлены не все возможности использования JUnit. Но как видно из приведенных примеров, фреймворк достаточно прост в использовании, дополнительных возможностей немного, но есть возможность расширения с помощью правил и запускалок.

12 инструментов для интеграционных и unit-тестов в Java

Резюме: хоть Maven и является инструментом, стандартизирующим структуру проекта и его сборку, однако он с крахом провалил стандартизацию разного рода тестов. Ща разберем полеты.

Итак, почему же все-таки Maven не справляется со стандартизацией тестов, ведь:

  • Выполняются на фазе test
  • Лежат в src/test каталоге


  • Названия тестовых классов содержат слово Test
    • Выполнаются на фазе integration-test
    • Лежат в src/test
    • В их имени должны быть буквы IT обозначающие собсно IntegrationTests.

Проблем на самом деле с этим несколько:

Фаза integration-test выполняется после модульных тестов, это значит что отдельно их не запустить. Каждый раз когда мы запускаем интеграционные тесты, выполняются модульные. Однако обычно мы хотим запустить модульные тесты один раз, а затем отдельно запускать интеграционные. Выходит чтоб модульные тесты не выполнялись нам нужно пропускать их с помощью -DskipTests , затем окажется что интеграционные тесты тоже не запускаются потому что failsafe плагин использует под собой surefire, начнется геморрой с созданием профилей и в конце концов начнет казаться что все это слишком сложно. Кстати, почему нам важно запускать тесты раздельно:

Разработчики могут быстро получить “зеленый” фидбек и продолжить работать. Именно модульные тесты способны быстро дать базовый ответ.

Немодульные тесты зависят от окружения, а значит могут падать. Если упали такие тесты, то еще нужно разобраться из-за окружения ли, либо и вправду сломалась логика.

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

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

Для системных тестов часто нужно подготовить окружение прежде чем их запускать.

Стандартно failsafe использует каталог src/test , а нам редко когда нужно помещать интеграционные и модульные тесты и ресурсы в одни и те же пакеты.

Фаза integration-test запускается перед install каждый раз. Мы же не хотим запускать медленные интеграционные тесты каждый раз когда устанавливаем артефакто в локальный репозиторий.

К сожалению для большинства разработчиков существуют лишь модульные, интеграционные тесты и “то, что делают QA”. Однако на самом деле тесты разделяются как минимум на модульные, системные и компонентные в зависимости от масштабов. Также есть функциональные и нефункциональные тесты и т.п. Более подробно можно с видами тестирования ознакомиться в одноименной статье. Но что главное — мы можем захотеть разделить все эти тесты. Для одних нужно поднимать все приложение, для других — лишь часть, для третьих вообще одного класса хватит. Однако Maven их никак не различает и не разделяет, у него есть либо модульные, либо интеграционные.

В общем подумав немного можно прити к выводу, что раз стандартный механизм Maven настолько несовершенен и все равно не сможет поддерживать всего нужного, мы можем отойти от него. Вместо этого предлагаю использовать plain old surefire plugin. Да, этот плагин заточен на написание модульных тестов, однако они по факту ничем не будут отличаться от “немодульных” — те же JUnit/TestNG будут описывать всю их логику (хотя тут позволяется также использовать всякие BDD фреймворки навроде JBehave, однако не о них речь).

Так вот как же это будет выглядеть. Для каждого из видов тестирования мы будем создавать а) профиль б) каталог с исходниками и ресурсами. Конфигурироваться же Maven будет следующим образом:

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


Модульные: mvn test

Компонентные: mvn test -Pcomponent-test

Системные: mvn test -Psystem-test

Структура каталогов тогда такая:

Правда системные тесты как правило имеет смысл выделять в отдельные модули, ато и проекты.

Единственное в чем я слукавил: фаза интеграционных тестов делиться на несколько шагов и это позволяет нам сначала поднять окружение, а затем его остановить. Подход с surefire такого не даст, вам придется вручную запускать окружение с помощью явно указанных команд. Однако:

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

Сложность в конфигурировании разного рода тестов перекрывает сложность запуска какого-нибудь tomcat:start

Т.к. обычно системные тесты выносят в отдельные модули или проекты, то там вполне можно использовать фазу integration-test как единственную, которая запускает тесты.

Но если вам правда хочется удобным образом автоматизировать развертывание окружения, возможно вам будет лучше сконфигурировать integration-test фазу для этого.

JUnit Categories, TestNG Groups

Последнее о чем стоит упомянуть — это JUnit категории и TestNG группы. Они позволяют с помощью аннотаций как-то помечать тесты. Однако если в TestNG это хоть как-то можно сделать удобно, то в JUnit это сделано через прямую кишку и вам придется конфигурить много профилей, у вас не выйдет задать exclude=IntegrationTest по умолчанию, а затем через командную строку активировать какие-то другие категории, для этого вам придется все равно создавать профили, а значит никакого удобства такой способ не принесет. Раз не выйдет это использовать с JUnit’ом, значит не имеет смысл стандартизировать такой подход.

    byСтароверъtestingmaven

3Comments

Максимум 3000 символов в коменте, поэтому пришлось разбить комментарий:

Проблема выявлена правильно но решена не теми средствами, профили здесь использовать ошибочно потому как:

Теперь нельзя в сборке запустить одновременно и юнит и интеграционные тесты, а между прочим такой сценарий сборки является самым распространненым, сборка на билд сервере должна осуществляться прогоном всех тестов, да и причина по которой мы можем захотеть запустить интеграционные тесты не запуская юнит тесты может быть только одна: в коде нет проблем, интеграционные тесты не выполнились потому что мы подготовили плохой тестовый набор, например не накатили апдейт схемы на базу, или подготовили неправильные датасеты для dbunit. А вот если интеграционные тесты нашли реальную ошибку в коде то их уже не в коем случае нельзя запускать без юнит тестов потому что, во первых у нас большая проблема в юнит тестах, они неполные потому как прошляпили ошибку, второе сама процедура исправления ошибки при agile такова что сначала нужно написать юнит тест выявляющий факт того что ошибка есть а уже потом вносить исправления в код. Ситуация когда мы хотим запустить юнит теста не запуская интеграционые это да, она реальна и я покажу ниже как её обойти.

Непонятно как это всё будет выглядеть в IDE, при предлагаемом подходе у нас будет одновременно под контролем компилятора и других валидаторов только одна папка с тестами, допустим правим код и юнит тесты и не видим что код интеграционных тестов больше не компилируется.

Теперь как я обыно разношу юнит и интеграционные тесты по разным фазам:

Интеграционные тесты просто ложим в паект integration и они не дергаются на фазе юнит тестирования, как собственно и на оборот. Как видно по дефолту юнит тесты запускаются всегда, что вроде бы логично, но мы можем их пропустить выставив property skip.unittest в true если нам надо.

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

Если всё таки хочется решать проблему именно через профили и никак иначе, например конкретно в вашем проекте по каким-то причинам это обоснованно, то выгоднее манипулировать элементом ‘includes’ вместо элементов ‘testResources’ и ‘testSourceDirectory’, так как перед нами изначально стоит проблема какие тесты запускать, элемент ‘includes’ даёт прямой и однозначный ответ на поставленный вопрос не мешая при использовании IDE компилятору проверять код тестов, а валидаторам ресурсов проверять тестовые ресурсы.

Заметки программистера

IT — это прекрасно!

Страницы

Как разделить модульные и интеграционные тесты с помощью Maven

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


В этой статье хотелось бы поделиться опытом ее использования.

Прежде всего надо отметить, что поумолчанию мавен считает тестами классы, чьи имена заканчиваются на Test. Интеграционные тесты принято именовать с суффиксом IT.

Стандартная структура мавен-проекта не предусматривает отдельной директории для интеграционных тестов. Тем не менее, удобно хранить интеграционные и модульные тесты в разных директориях. Для реализации этой идеи существует плагин build-helper-maven-plugin.

Для интеграционных тестов я использую директорию integration-test, которая, по аналогии с директорией test, находится в папке src:

Если следовать конвенции об именовании тестов (заканчивать имена модульных тестов *Test, а интеграционных *IT), то проблем с интеграционными тестами на этапе модульного тестирования у вас не возникнет.

Но на этапе непосредственно интеграционного тестирования счастья тоже не произойдет. Для счастья придется потрудиться:

Плагин maven-failsafe-plugin специально предназначен для конфигурирования интеграционных тестов. Приведенный пример конфигурации этого плагина вряд ли требует пояснений.

Кстати, по аналогии существует плагин и для конфигурирования модульных тестов. Называется maven-surefire-plugin. И для порядка, можно явно воспользоваться им:

JUnit — введение в юнит-тесты. Пример JUnit Hello world

Введение в юнит-тестирование с помощью библиотеки JUnit. Пример JUnit Hello world

Используемые технологии и библиотеки

1. Описание задачи

Создать юнит тесты для приложения «калькулятор». Показать применение базовых аннотаций JUnit,

2. Структура проекта

Класс Calculator описывает простые арифметические операции. Этот класс послужит основой для написания юнит тестов. Тестирующие классы находятся в пакете test .

3. pom.xml

Для подключения библиотеки JUnit используется maven.

Других зависимостей подключать не нужно.

4. Calculator

Наш простой класс, описывающий калькулятор описан чуть ниже.

5. Создание тестирующих классов

В IntelliJ IDEA можно создать тестирующих класс автоматически. Для этого можно нажать alt + enter на классе и выбрать «Create test». Далее выбрать методы, которые нужно будет протестировать. В результате будет создан класс CalculatorTest с тремя выбранными методами. Эти методы необходимо реализовать самостоятельно.

6. CalculatorTest

После создания тестирующего класса нам необходимо реализовать методы, которые мы хотим проверить. Так же были добавлены другие методы, которые будут демонстрировать работу базовых JUnit аннотаций.

Тестирование с помощью TestNG в Java

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

TestNG – это фреймворк для тестирования, написанный на Java, он взял много чего с JUnit и NUnit, но он не только унаследовался от существующей функциональности Junit, а также внедрил новые инновационные функции, которые делают его мощным, простым в использовании.

TestNG предназначен для:

• интеграционного тестирование и т.д.

Какие возможности в TestNG?


1) Annotations. (Аннотации);

2) Использование XML для гибкого конфигурирования тестов;

3) Поддержка data-driven тестирования (с помощью аннотации @DataProvider);

4) Зависимые методы для тестирования серверных приложений;

5) Поддерживается в Eclipse, IDEA, Ant, Maven, Netbean, Hudson;

6) Тестирование вашего кода проходит многопоточно, что дает безопасность и быстродействие;

7) Легкий переход от JUnit.

Содержание урока

Шаг 1. Начало

Давайте напишем первый наш тест используя TestNG.

Для этого я буду использовать среду разработки Intellij IDEA 12.

Создаем Maven проект и добавляем зависимости:

После этого создадим класс Calc.java и тестовый класс CalcTest.java для него.

Содержимое класса Calc.java:

Содержимое класса CalcTest.java:

7-я строка – это экземпляр класса который мы будем тестировать.

9-я строка – эта аннотация говорти о том что данный метод есть тестовым и может запускаться в отдельном потоке.

Шаг 2. Первый тест

Давайте напишем тест на метод в классе Calc.java:

У нас есть класс, в котором есть метод, который находил сумму двух чисел. Для него мы написали тест, где проверили, что сумма числа 2 и 3 должна быть равна 5-ти.

С помощью класса Assert библиотеки TestNG мы проверяем на правильность работы метода.

Шаг 3. Запуск тестов

Теперь запускаем написанный тест.

После выполнения теста вы увидите следующий результат:

Мы видим, что тест удачно прошел!

После удачного прохождения тестов формируется специальный xml файл, с его помощью мы можем прогонять тесты повторно, используя консоль cmd.

Запустить тесты можно через консоль используя файл temp-testng-customsuite.xml, для этого войдите в cmd и введите команду:

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

Шаг 4. Аннотации TestNG


TestNG является более гибким благодаря своим аннотациям. Что они нам предоставляют?

Значит существует 10 управляемых аннотаций TestNG:

1. @BeforeSuite – указывает, что данный метод будет запускаться перед любым методом тестового класса.

2. @BeforeGroups – аннотирует методы, которые будут выполняться перед первым методом в любой из указанных групп.

3. @BeforeClass – указывает, что метод будет выполнен до всех тестовых методов тестового класса.

4. @BeforeTest – аннотированный метод будет запускаться до всех тестовых методов.

5. @AfterTest – аннотированный метод будет запущен после всех тестовых методов, принадлежащих классам внутри тега .

6. @BeforeMethod – аннотированный метод будет выполняться перед каждым тестовым методом.

7. @AfterMethod – аннотированный метод будет запускаться после каждого тестового метода.

8. @AfterClass – аннотированный метод будет запущен после всех тестовых методов в текущем классе.

9. @AfterGroups – аннотируется методы, которые будут выполняться после всех методом в любом из указанных групп.

10. @AfterSuite – указывает, что данный метод, будет запускаться после всех методов тестового класса.

Вот, что у нас должно получится:

И вот результат:

Шаг 5. Исключения

Отловить ожидаемую ошибку можно с помощью аннотации @Test через параметр expectedExceptions:

где expectedExceptions – ожидаемое исключение(ошибка).

В этом случае тест пройдет успешно, так как мы ожидаем, что данный тест выбросит нам NullPointerException.

Шаг 6. Игнорирование тестовых методов

Иногда требуется проигнорировать тестовый метод, если вам например в очередном тестировании не требуется выполнять данный тестируемый метод.

Для того, чтобы проигнорировать тестовый метод, вам достаточно указать в аннотации @ Test параметре enabled = false.

Шаг 7. Timeout

Если вам потребуется ограничить время проведения теста для определенного тестового метода, то вам на помощь приходит параметр timeOut аннотации @Test.

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

В 6-й строке мы заставляем поток заснуть на 1001 миллисекунду.

Шаг 8. Групповое тестирование

Если вам нужно прогнать тесты группами, например, чтобы тестовые методы выполнялись по очереди группами.

Для этого мы можем использовать атрибут groups в аннотации @Test.

Следующий пример имеет 4 тестовых метода, каждый из них входит в группу интеграции, методы testingMethod1, testingMethod3 testingFMethod4 входят в группу Unit1. testingMethod2 входит в группу unit2.


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

Для этого нажимаем правой кнопкой мыши по названию тестового класса:

После, в появившимся окне делаем следующие действия познанные цифрами:

После этого жмем Run и в результате выполнения вы увидите, что выполнился только 2 тестовых метод, которые относятся к группе unit2:

Шаг 9. Зависимые тесты

Допустим, есть тестовый класс, в котором есть 2 тестовых метода, где один зависит от другого, то есть первый не пройдет успешно, пока не отработает второй. Давайте рассмотрим, как это сделать.

Сделать это можно с помощью параметра dependsOnMethods аннотации @Test:

Как видите, тестовый метод testmethod() зависит от тестового метода initEnvironmentTest() и будет выполнен только после того, как отработает initEnvironmentTest() метод.

Также зависимость можно установить на группу тестов:

Сначала выполнится группа тестов, а потом зависимые от это группы тесты.

ru_java

ru.java

все о языке программирования java

Хочется обсудить кто как тестирует классы работы с БД.
Когда возникла острая необходимость мною была реализована следующая схема:

База перед тестами вычищается. Каждый тест перед тестированием собственно метода работы с БД создает все необходимые связанные сущности, а потом только создав объект на основе этих сущностей запускает тесты для него.
Минусы: в каждом тесте есть куча дублирующегося кода, очень сложно поддерживать целостность кода и целостность БД
Плюсы: быстро реализуется, тестовая база всегда быстро пополняется данными.

Есть задумка сделать так:
Создать эталонную БД с данными. Перед началом тестов делать её полную копию (схема и данные) и на эту копию натравливать тестируемые классы. После проработки каждого теста базу чистить от данных и снова копировать их из эталонной.

Посмотрите как Рубин на Рельсах это делает. У них система сама копирует базу данных в тестовую, и с тестовой и балуется.

Но это если с базой: имеет смысл ещё побаловаться с mock objects, before you get to database connection.

Трудность заключается в том, что используется самописный JDBC-пул который держит открытыми Connections, а классы которые хотят обратиться к БД обращаются к этому пулу и получают, а затем отдают обратно соединения. По идее это бы надо заменить чем-нибудь поинтереснее, но мне с этим приходится мириться. Spring и Hibernate использовать уже было нельзя, на тот момент когда я созрел для их внедрения. Слишком много кода уже было написано.

Так вот о чем это я 🙂
Идея откатывать транзакции была, но с этим пулом такая штука не проходит 🙁 Фабрики которые работают с DAO слишком связаны друг с другом и когда тестируется одна фабрика она вызывает много других которые самостоятельно берут себе соединения из пула и получается, что все транзакции откатить не получается.
Вот такие грабли.

Кстати может кто предложит замену этой приблудине под названием JDBC-пул? Просто в сети с таким функционалом решений я не нашел.
От него нужно: кэшировать соединения, чтобы не открывать их много раз ибо мееедленно (для Oracle почти 10 секунд или я ошибаюсь?)
Насчет кэширования запросов — не совсем понятно, вроде как это должна делать СУБД сама или драйвер?

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

Хочется все и сразу в одной библиотеке 🙂

Много читал про Hybernate, но так до конца и не разобрался с их системой кеширования. Она обеспечит такие вот вещи или хотябы подобные?

Немного о java

Unit-tests(Модульное тестирование) на примере Java. Часть первая. Тестируемый код.

Оценить эту запись

Предисловие.
Сначала хотел написать сразу и обо всем, но понял, что получиться слишком много. Поэтому разбил на несколько частей. И для начала немного философии под названием — «Тестируемый код». О Java и Инструментах тестирования тут будет сказано мало. Первая часть — больше теоретическое руководство и моральная подготовка.

Приступим.
Модульное тестирование на wiki– достаточно сухо и понятно рассказывает о том, что это такое.
Суть такая. Модульное тестирование позволяет контролировать правильность выполнения кода еще на этапе разработки.
Это актуально если у вас:

  1. Большой проект или в него часто вносятся изменения.
  2. Много разработчиков.



Это очень приятно иметь возможность на этапе сборки или разработки отследить не сломалось ли что-то в результате внесенных изменений и не краснеть перед заказчиком.
Однако использование модульного тестирования при разработке имеет некоторые последствия. Оказывается влияние на качество кода и архитектуру приложения. На мой взгляд влияет это самым благотворным образом.
Когда впервые сталкиваешься с написанием модульных тестов, возникает две проблемы:

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

Эти две проблемы вовлекают нас в мир ООП и в понимание принципа Inversion of Control(Инверсии управления). Собственно ООП нам понадобиться для реализации принципа IoC.
В чем проблема, лучше разбираться на примере.
Пример очень абстрактен, но нам и не нужен конкретный код.Представьте себе вот такой набор методов. Суть — обработать данные:

  1. Отправить данные на удаленный веб-сервер.
  2. Выполнить некоторые расчеты над данными.
  3. Сохранить данные в БД.

(Внимание очень плохой код! Никогда так не делайте)

Что тут плохого:

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

И так давайте по первому пункту. Просто представьте, что вам необходимо протестировать метод doWork. Для этого вам потребуется — база данных и удаленный веб сервис. Вам надо будет проверить, что на удаленный веб сервис пришли данные, в БД данные были записаны с учетом вычислений которые были произведены над ними. Фактически вам придется поднять всю инфраструктуру проекта и руками смотреть результаты вычислений. Это потеря времени. Иногда бывает еще хуже — ни структура базы данных, ни веб-сервис еще не сделаны, а вам уже надо реализовать данный код. Как тогда быть?

Второй пункт — проблема заключается в том, что функция отправки данных на удаленный веб-сервис внутри себя вызывает функцию расчетов. Согласитесь — это не логично, а функция расчетов вызывает метод сохранения в базу данных. Верю, что название метода должно соответствовать тому, что он делает. Рано или поздно — вам потребуется отослать данные на удаленный веб-сервис не проводя вычислений и не сохраняя их в БД. И если это будет делать программист(не вы, а кто-то другой) он просто вызовет метод sendDataToRemoteService, и потом долго будет удивляться почему в базе появляются лишние данные.

Я ведь вроде как собрался рассказывать о модульном тестирование, а перешел на поучения «Как не стоит писать код». Дело в том — что одно от другого не отделимо. Что бы легко и просто тестировать код, он должен быть написан определенным образом.
Для себя я вывел правило: чем больше код напоминает «лапшу» тем сложней его тестировать.
Отсюда вывод: если у вас получаются очень сложные тесты, то стоит заняться рефакторингом и архитектурой программы.

Делаем код тестируемым.
Первое, с чего надо начать — сделать выполнение кода наглядным. Делаем последовательность вызова методов независимыми друг от друга.

Это достаточно просто. Дальше начинается то, что многим может показаться излишним. Но это только так кажется, т. к. пример маленький и тривиальный.
Очевидно, что класс DataWorkflow выполняет сразу три несвязанные друг с другом функции: вычисление, обращение к удаленному сервису, работа с бд. Нашей задачей будет разорвать данную сцепку и создать 3 сервиса которые бы занимались каждый своим делом и еще 1 сервис который бы ими управлял. Собственно реализуем принцип инверсии управления.

И так, когда сервисы готовы, передаем управление ими классу DataWorkflow.

Что мы теперь имеем. У нас 4 класса, три из них делают конкретные задачи, 4-ый обеспечивает последовательность выполнения команд.

Что нам это дает с точки зрения модульного тестирования и зачем нужно было это делать?
Бонус состоит в том, что вместо 1-го сложного, комплексного теста, мы можем написать 4-ре достаточно простых теста.

  1. Тест RemoteService — должен проверяет работу с удаленным сервером.
  2. Тест CalcService – должен проверять правильность вычислений.
  3. Тест DBService — должен проверять правильность сохранения в базу данных.
  4. Тест DoSomeWork — должен проверять последовательность и параметры вызова методов sendDataToRemoteService, calculate, saveToDB. Внимание — только это и ничего более. Что правильность работы этих методов проверенна тестами 1,2,3. У нас нет необходимости тестировать их еще раз. Правда здорово ?

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

Класс DataWorkflow остается без изменений, изменяется способ его инициализации.
Будет примерно так

DataWorkflow dataWorkFlow = new DataWorkFlow(new RemoteServiceImpl(), new CalcServiceImpl(), new DBServiceImpl());

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

  1. Отделение «бизнес логики» от логики обработки и передачи данных. В результате код становиться наглядней, каждый класс занимается своим делом.
  2. Получаем структуру проекта в виде нескольких классов и интерфейсов. Это очень удобно если разработкой занимается несколько человек.
  3. Для реализации логики используем агрегирование сервисов, что позволяет нам в случаи необходимости подменять их реализацию. Это очень удобно для модульного тестирования.
  4. Очень легко тестировать работу со внешними источниками данных, т.к. не операции стали атомарны. Нет необходимости тестировать сразу весь метод, можно по отдельности тестировать работу с БД, Веб-сервисом, работу вычислений. Так же появляется возможность контролировать логику выполнения задач.

А теперь немного из практики. Часто использую модульное тестирования для того, что бы разобраться с новой технологией. Удобно тем, что не надо писать отдельное приложение для тестов, да и написанный код получается не бесполезным, а тестирует реальную функцию. Еще практика показывает, что многие приложения достаточно долго запускаются, или для того что бы протестировать какую-то функцию нужно сделать очень много действий. Модульный тест поможет избежать этих проблемы, т. к. для его выполнения не требует запуска всех систем программы, а дынные уже заранее приготовлены. Еще одно, возня с написанием тестов позволяет еще на стадии его написания выявить кучу глупых ошибок, опечаток и.т.п.

Помимо плюсов, есть и минусы.

  • Количество написанного кода значительно увеличивается.
  • Очень сложно осуществлять поддержку тестов, если приложение серьезно меняется. Нужно будет дорабатывать и переписывать существующие тесты. Это достаточно много времени и сил.

В заключении анонс.
Следующая часть будет посвящена инструменту для организации модульного тестирования на java – jUnit. Пошаговый разбор написания теста и конечно исходники примеров которые можно будет потрогать и запустить. А так же узнаем что такое Mock и как технология используется в тестирование.
Далее в планах — тестирование работы с бд, тестирование работы с ldap, тестирование методов использующих hibernate + spring.

Maven surefire и failsafe плагины. Используем вместе и по отдельности

Автор: beowulf13th · 12 мая 2020

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

integration-test false true it/** ut/**

Объяснения. Для начала в свойства по умолчанию добавляем отлючение интеграционных тестов. Потому что интеграционные тесты могут занимать достаточно больше количество времени и нам не всегда нужно их гонять. Это мы делаем тэгом .

После этого добавляем два плагина в нашу сборку.

За запуск юнит тестов отвечает maven-surefire-plugin. Он по умолчанию стартует только тесты, которые отвеачют следующему соглашению имён:

Но в моём проекте, для которого я это делал требование (которое придумал Atlassian), что все юнит тесты лежат в пакетах, начинющихся на ut, а интеграционные на it. Поэтому у меня в настройку плагина включаются и исключаются определённые пакеты. По идее, если вы будете следовать соглашению имён в плагинах — у вас тоже будет всё работать.

За запуск интеграционных тестов отвечает maven-failsafe-plugin. Его тоже нужно настроить. Для этого нужно как минимум указать ему, что интеграционные тесты нужно снова гонять, а юнит тесты нас не интересуют:

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

Но, как я говорил, есть некоторые проектные требования 🙂

Можем ли мы использовать JUNIT для тестирования автоматизированной интеграции?

Как вы автоматизируете интеграционное тестирование? Я использую JUnit для некоторых из этих тестов. Это одно из решений или совершенно неправильно? Что вы предлагаете?

Работает JUnit. Нет ограничений, которые ограничивают его только модульными испытаниями. Мы используем JUnit, Maven и CruiseControl для выполнения CI.

Могут быть инструменты, специфичные для тестирования интеграции, но я думаю, что их полезность зависит от того, какие типы компонентов системы вы интегрируете. JUnit отлично работает для тестирования типа UI.

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

Вот подход, который хорошо подходит для приложений, использующих http и базы данных, и я хочу проверить весь стек:

  • Используйте Hypersonic or H2 в режиме памяти в качестве замены базы данных (это лучше всего подходит для ORM)
  • Инициализировать базу данных в @BeforeSuite или эквивалентном (снова: проще всего с ORM)
  • Используйте Jetty для запуска веб-сервера в процессе.
  • @Before каждый тест, очистить базу данных и инициализировать необходимыми данными
  • Используйте JWebUnit для выполнения HTTP-запросов к Jetty

Это дает вам интеграционные тесты, которые могут запускаться без какой-либо настройки сервера базы данных или приложений, и который выполняет стек с http down. Поскольку он не имеет зависимости от внешних ресурсов, этот тест отлично работает на сервере сборки.

Мастер Йода рекомендует:  Вставка клипов на страницу
Добавить комментарий