Прежде всего, весь исходный код для этого доступен на 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: Оптимизация и анализ исходного кода
- Вещи, которые должен знать каждый программист
- Почему автоматизированные тесты ускоряют вашу разработку
