Прежде всего, весь исходный код для этого доступен на github: https://github.com/activelylazy/coverage-example . Я покажу ключевые фрагменты, но очевидно, что здесь пропущено много деталей, которые (надеюсь) не имеют значения.
Пример приложения
Вместо того, чтобы порвать с традицией, пример приложения представляет собой простой, хотя и немного надуманный, привет мир:
Как это работает
Стартовая страница — это простая ссылка на страницу приветствия:
1
2
|
< h1 >Example app</ h1 > < p >See the < a id = "messageLink" href = "helloWorld.html" >message</ a ></ p > |
Страница Hello World просто отображает сообщение:
1
2
|
< h1 >Example app</ h1 > < p id = "message" >< c:out value = "${message}" /></ p > |
Контроллер hello world отображает представление, передавая сообщение:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public class HelloWorldController extends ParameterizableViewController { // Our message factory private MessageFactory messageFactory; @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { // Get the success view ModelAndView mav = super .handleRequestInternal(request, response); // Add our message mav.addObject( "message" ,messageFactory.createMessage()); return mav; } @Autowired public void setMessageFactory(MessageFactory messageFactory) { this .messageFactory = messageFactory; } } |
Наконец, MessageFactory просто возвращает жестко закодированное сообщение:
1
2
3
|
public String createMessage() { return "Hello world" ; } |
Юнит тест
Мы определяем простой модульный тест, чтобы убедиться, что MessageFactory ведет себя как ожидалось:
01
02
03
04
05
06
07
08
09
10
11
12
|
public class MessageFactoryTest { // The message factory private MessageFactory messageFactory; @Test public void testCreateMessage() { assertEquals( "Hello world" ,messageFactory.createMessage()); } @Autowired public void setMessageFactory(MessageFactory messageFactory) { this .messageFactory = messageFactory; } } |
Сложение
Для сборки этого и запуска модульного теста достаточно простого файла maven pom. На данный момент у нас есть работающее приложение с модульным тестом основных функций (таких как оно), которые мы можем создать и запустить.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
< project > < modelVersion >4.0.0</ modelVersion > < groupId >com.example</ groupId > < artifactId >helloworld</ artifactId > < packaging >war</ packaging > < version >1.0-SNAPSHOT</ version > < name >helloworld Maven Webapp</ name > < build > < finalName >helloworld</ finalName > </ build > < dependencies > ...omitted... </ dependencies > </ project > |
Покрытие кода
Теперь давайте интегрируем Эмму, чтобы мы могли получить некоторые отчеты о покрытии кода. Во-первых, мы определяем новый профиль Maven, это позволяет нам контролировать, будем ли мы использовать emma в любой данной сборке.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
< profile > < id >with-emma</ id > < build > < plugins > < plugin > < groupId >org.codehaus.mojo</ groupId > < artifactId >emma-maven-plugin</ artifactId > < inherited >true</ inherited > < executions > < execution > < id >instrument</ id > < phase >process-test-classes</ phase > < goals > < goal >instrument</ goal > </ goals > </ execution > </ executions > </ plugin > </ plugins > </ build > </ profile > |
Это просто вызывает цель «инструмента» во время фазы Maven «процесс-тест-классы»; то есть, как только мы скомпилировали наши файлы классов, используйте emma для их инструментов. Мы можем запустить это, вызвав maven с новым профилем:
mvn clean install -Pwith-emma |
После завершения сборки мы можем запустить Emma для генерации отчетов о покрытии кода:
В Windows:
java - cp %USERPROFILE%/.m2 /repository/emma/emma/2 .0.5312 /emma-2 .0.5312.jar emma report -r xml,html - in coverage.ec - in target /coverage .em |
В Linux:
java - cp ~/.m2 /repository/emma/emma/2 .0.5312 /emma-2 .0.5312.jar emma report -r xml,html - in coverage.ec - in target /coverage .em |
Теперь мы можем просмотреть отчет о покрытии в формате HTML в cover / index.html. На данный момент, это показывает, что у нас есть 50% тестовое покрытие (по классам). MessageFactory полностью покрыт, но HelloWorldController вообще не имеет никаких тестов.
Интеграционный тест
Чтобы протестировать наш контроллер и JSP, мы будем использовать WebDriver для создания простого интеграционного теста; это тест JUnit, который запускает браузер.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class HelloWorldIntegrationTest { // The webdriver private static WebDriver driver; @BeforeClass public static void initWebDriver() { driver = new FirefoxDriver(); } @AfterClass public static void stopSeleniumClent() { try { driver.close(); driver.quit(); } catch ( Throwable t ) { // Catch error & log, not critical for tests System.err.println( "Error stopping driver: " +t.getMessage()); t.printStackTrace(System.err); } } @Test public void testHelloWorld() { // Start from the homepage HomePage homePage = new HomePage(driver); HelloWorldPage helloWorldPage = homePage.clickMessageLink(); assertEquals( "Hello world" ,helloWorldPage.getMessage()); } } |
Строки 4-18 просто запускают Web Driver перед тестом и закрывают его (закрывая окно браузера) после завершения теста.
В строке 22 мы переходим на домашнюю страницу с жестко заданным URL-адресом.
В строке 23 мы инициализируем наш объект страницы веб-драйвера для домашней страницы. Это включает в себя все детали работы страницы, позволяя тесту функционально взаимодействовать со страницей, не беспокоясь о механике (какие элементы использовать и т. Д.).
В строке 24 мы используем объект домашней страницы, чтобы щелкнуть ссылку «сообщение»; это переходит на страницу приветствия.
В строке 25 мы подтверждаем, что сообщение, отображаемое на странице приветствия, соответствует ожиданиям.
Примечание. Я использую объекты страницы для отделения спецификации теста (что делать) от реализации теста (как это сделать). Подробнее о том, почему это важно, смотрите, как не допускать ломкости тестов .
домашняя страница
Объект домашней страницы довольно прост:
1
2
3
4
|
public HelloWorldPage clickMessageLink() { driver.findElement(By.id( "messageLink" )).click(); return new HelloWorldPage(driver); } |
HelloWorldPage
Страница «Привет, мир» одинаково проста:
1
2
3
|
public String getMessage() { return driver.findElement(By.id( "message" )).getText(); } |
Выполнение интеграционного теста
Чтобы запустить интеграционный тест во время нашей сборки Maven, нам нужно внести несколько изменений. Во-первых, нам нужно исключить интеграционные тесты из этапа модульных тестов:
01
02
03
04
05
06
07
08
09
10
11
12
|
< plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-surefire-plugin</ artifactId > ... < configuration > ... < excludes > < exclude >**/*IntegrationTest.java</ exclude > < exclude >**/common/*</ exclude > </ excludes > </ configuration > </ plugin > |
Затем мы определяем новый профиль, чтобы мы могли дополнительно запускать интеграционные тесты:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
< profile > < id >with-integration-tests</ id > < build > < plugins > < plugin > < groupId >org.mortbay.jetty</ groupId > < artifactId >maven-jetty-plugin</ artifactId > < version >6.1.22</ version > < configuration > < scanIntervalSeconds >5</ scanIntervalSeconds > < stopPort >9966</ stopPort > < stopKey >foo</ stopKey > < connectors > < connector implementation = "org.mortbay.jetty.nio.SelectChannelConnector" > < port >9080</ port > < maxIdleTime >60000</ maxIdleTime > </ connector > </ connectors > </ configuration > < executions > < execution > < id >start-jetty</ id > < phase >pre-integration-test</ phase > < goals > < goal >run</ goal > </ goals > < configuration > < daemon >true</ daemon > </ configuration > </ execution > < execution > < id >stop-jetty</ id > < phase >post-integration-test</ phase > < goals > < goal >stop</ goal > </ goals > </ execution > </ executions > </ plugin > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-surefire-plugin</ artifactId > < version >2.5</ version > < inherited >true</ inherited > < executions > < execution > < id >integration-tests</ id > < phase >integration-test</ phase > < goals > < goal >test</ goal > </ goals > < configuration > < excludes > < exclude >**/common/*</ exclude > </ excludes > < includes > < include >**/*IntegrationTest.java</ include > </ includes > </ configuration > </ execution > </ executions > </ plugin > </ plugins > </ build > </ profile > |
Это может показаться сложным, но на самом деле мы просто настраиваем Jetty для запуска во время наших интеграционных тестов; затем настройте сами, как запускать интеграционные тесты.
В строках 9-19 настройте причал — порт для запуска и способ его остановки.
Строки 21-30 настраивают причал для работы во время фазы «предварительной интеграции» сборки maven.
Строки 31-37 конфигурируют причал, который должен быть остановлен на этапе «тестирования после интеграции» сборки maven.
В строках 40-62 мы снова используем подключаемый модуль maven-surefire-plugin, на этот раз для запуска на этапе «интеграция-тест» сборки, только с нашими классами интеграционного тестирования.
Мы можем запустить эту сборку с:
mvn clean install -Pwith-emma -Pwith-integration-tests |
Это создаст все, запустит юнит-тесты, построит войну, запустит пристань, чтобы принять войну, запустит наши интеграционные тесты (вы увидите всплывающее окно firefox, в то время как остальные запускаются), а затем отключите пристань. Поскольку война построена с использованием инструментальных классов, Эмма также отслеживает покрытие кода, пока мы проводим наши интеграционные тесты.
Теперь мы можем создавать наше приложение, запускать модульные и интеграционные тесты, собирать комбинированные отчеты о покрытии кода. Если мы повторно запустим отчет emma и проверим покрытие кода, мы увидим, что у нас есть 100% тестовое покрытие — поскольку контроллер также покрывается тестами.
вопросы
Какие нерешенные проблемы с этим, какие дополнительные расширения могут быть сделаны?
- Сборка создает инструментированный WAR — это означает, что вам нужно запустить вторую сборку без emma, чтобы получить готовую сборку.
- Тест интеграции жестко кодирует порт, на котором настроен Jetty; Это означает, что тесты не могут быть запущены непосредственно в Eclipse. Можно передать этот порт, по умолчанию, скажем, 8080, так что интеграционные тесты могут без проблем запускаться в Eclipse через сборку maven.
- При работе на сервере сборки вы, вероятно, не хотите, чтобы Firefox появлялся случайно (если X даже установлен); так что запуск xvfb — хорошая идея. Можно настроить maven для запуска и остановки xvfb до и после интеграционных тестов.
Ссылка: покрытие кода с модульными и интеграционными тестами и от нашего партнера JCG Дейва в блоге Actively Lazy
- Услуги, практики и инструменты, которые должны существовать в любом доме разработки программного обеспечения, часть 1
- Услуги, практики и инструменты, которые должны существовать в любом доме разработки программного обеспечения, часть 2
- Это приходит ДО вашей бизнес-логики!
- Сколько ошибок в вашем коде?
- Использование FindBugs для создания значительно меньшего количества ошибочного кода
- Инструменты Java: Оптимизация и анализ исходного кода
- Вещи, которые должен знать каждый программист
- Почему автоматизированные тесты ускоряют вашу разработку