Статьи

Интеграционное тестирование Maven и Spring Restful Services

Введение

Мой оригинальный блог показал, как разделить тестовые модули maven и интеграционные тесты на очень простом примере. http://johndobie.blogspot.com/2011/06/seperating-maven-unit-integration-tests.html С тех пор многие люди спрашивали меня о более реалистичном примере, чем тот, который использовался изначально. В этом посте показано, как разделить ваши модульные и интеграционные тесты с использованием оригинального метода в реалистичной среде, где приложение фактически развернуто на сервере.
  • Мы используем Maven для сборки и тестирования некоторых успокоительных веб-сервисов на основе Spring.
  • Затем мы используем плагин Maven Jetty для запуска веб-сервера и развертывания их на нем.
  • Мы создаем базу данных в памяти и создаем схему
  • Наконец, мы запускаем все интеграционные тесты в отдельном каталоге \ src \ integratest \ java
Цель этой статьи — показать, как реалистично использовать Maven для запуска и развертывания набора служб на работающем сервере перед запуском интеграционных тестов. Речь идет не о тонких деталях REST или Spring MVC. Я расскажу об этом достаточно легко, чтобы создать работающее приложение, и в то же время предоставлю ссылки на более глубокие статьи для тех, кому нужна дополнительная информация.

Структура кода

Выполнение примера

Полный код размещен на Google Code. Используйте следующие команды, чтобы проверить и запустить его. Убедитесь, что на порте 8080 ничего не работает, прежде чем запускать тесты.

1
2
3
svn co https://designbycontract.googlecode.com/svn/trunk/examples/maven/spring-rest-example
cd spring-rest-example
mvn clean install -Pit,jetty

Вы можете увидеть полную сборку на следующем экземпляре Jenkins, размещенном в Cloudbee. https://designbycontract.ci.cloudbees.com/job/spring-rest-example/

Результаты запуска примера

  • Испытания в стандартной тестовой структуре maven проводятся, как обычно, на этапе модульных испытаний.
  • Запущен веб-сервер Jetty
  • Война, содержащая веб-сервер, развернута на сервере
  • База данных hsqldb в памяти запущена и схема создана.
  • Тесты в каталоге \ src \ integratest \ java выполняются на этапе тестирования интеграции.
  • Сервер выключен.

Как создать класс Spring Service

Торговый сервис очень прост. Он использует репозиторий для создания и поиска сделок. Я не включил исключения, чтобы сделать все как можно более простым. Единственная хитрость здесь — добавить аннотацию @Service, иначе это прямая Java.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Service
public class SimpleTradeService implements TradeService {
  @Autowired
  TradeRepository tradeRepository;
  
  public SimpleTradeService(TradeRepository tradeRepository)  {
    this.tradeRepository = tradeRepository;
  }
  
  @Override
  public Long createTrade(Trade t) {
    Long id = tradeRepository.createTrade(t);
    return id;
  }
 
  @Override
  public Trade getTradeById(Long id) {
    return tradeRepository.getTradeById(id);
  }

Как создать класс репозитория базы данных

Вышеуказанный сервис использует торговый репозиторий для создания и поиска сделок. Мы используем класс Spring HibernateDaoSupoort, чтобы создать этот класс и упростить его. Расширяя этот класс, нам просто нужно создать наш класс торговых объектов и определить детали нашей базы данных в весеннем конфиге. Все остальные детали позаботятся об этом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class HibernateTradeRepository  extends HibernateDaoSupport implements TradeRepository{
  @Override
  public Trade getTradeByReference(String reference) {
       throw new RuntimeException();
  }
 
  @Override
  public Long createTrade(Trade trade) {
      return (Long) getHibernateTemplate().save(trade);
  }
 
  @Override
  public Trade getTradeById(Long id) {
      return getHibernateTemplate().get(Trade.class, id);
  }
}

Как создать базу данных Trade Class

Мы используем стандартные аннотации JPA для определения торгового объекта нашей базы данных.

1
2
3
4
@Entity
public class Trade {
 @Id
 private long id;

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

Как настроить базу данных

Для этого примера мы будем использовать Hsqldb для создания нашей базы данных. http://hsqldb.org/ Новый экземпляр этого будет создаваться каждый раз, когда мы запускаем сервер. Для настройки базы данных все, что нам нужно сделать, это определить ее в весеннем конфиге trade-servlet.xml

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
<bean id="sessionFactory"  
<bean id="sessionFactory"  class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="packagesToScan"
      value="com.dbc.model" />
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.format_sql">true</prop>
        <prop key="hibernate.transaction.factory_class">
          org.hibernate.transaction.JDBCTransactionFactory
        </prop>
        <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
        <prop key="hibernate.connection.pool_size">0</prop>
        <prop key="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</prop>
        <prop key="hibernate.connection.url">
          jdbc:hsqldb:target/data/tradedatabase;shutdown=true
        </prop>
        <prop key="hibernate.connection.username">sa</prop>
        <prop key="hibernate.connection.password"></prop>
        <prop key="hibernate.connection.autocommit">true</prop>
        <prop key="hibernate.jdbc.batch_size">0</prop>
        <prop key="hibernate.hbm2ddl.auto">update</prop>
      </props>
    </property>
  </bean>

Фабрика сеансов определяет детали подключения к нашей базе данных. Наиболее важным свойством является

1
<prop key="hibernate.hbm2ddl.auto">update</prop>

Это свойство сообщает hibernate об обновлении базы данных при запуске приложения. Это эффективно создаст таблицу для торгового объекта из аннотаций на нашем торговом объекте. Когда вы запустите тесты, вы увидите, что следующий SQL выполняется при запуске.

1
2
3
4
5
6
11:30:31,899 DEBUG org.hibernate.tool.hbm2ddl.SchemaUpdate SchemaUpdate:203
- create table
Trade (id bigint          not null,
       description        varchar(255),
       reference          varchar(255),
       primary key (id))

Это новая база данных и готова к работе.

Создание Restful интерфейса.

Я просто собираюсь осветить основы здесь. Для некоторых замечательных примеров перейдите по этим ссылкам http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/ http://www.stupidjavatricks.com/?p=54

Как создать Spring Controller

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@RequestMapping(value = "/create/trade/{id}")
public ModelAndView createTrade(@PathVariable Long id) {
  Trade trade = new Trade(id);
  service.createTrade(trade);
  ModelAndView mav = new ModelAndView("tradeView", BindingResult.MODEL_KEY_PREFIX + "trade", trade);
  return mav;
}
 
@RequestMapping(value = "/find/trade/{id}")
public ModelAndView findTradeById(@PathVariable Long id) {
  Trade trade = service.getTradeById(id);
  ModelAndView mav = new ModelAndView("tradeView", BindingResult.MODEL_KEY_PREFIX + "trade", trade);
  return mav;
}

Это работает довольно просто, заполняя идентификатор @PathVariable значением из / find / trade / {id} Например, запрос / find / trade / 1 заполнит ссылку на «1», запрос / find / trade / 29 заполнит ссылку на «29» Более подробную информацию можно найти здесь: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-uri-templates

Как настроить веб-приложение

Конфигурация веб-приложения в web.xml очень проста. Сначала мы регистрируем Spring Servlet

1
2
3
4
trade
org.springframework.web.servlet.DispatcherServlet
1

Далее мы определяем отображение на сервлет. Это отображение передаст все запросы нашему сервлету.

1
2
trade
/*

Как настроить Spring

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

1
 

BeanNameViewResolver берет имя

1
 

Этот пугающе выглядящий фрагмент XML выполняет работу по обеспечению того, чтобы объект Trade возвращался как XML. XStream возьмет объект и автоматически преобразует его в формат XML.

1
2
3
 

Класс Trade определяет для этого аннотацию XStream.

1
2
@XStreamAlias("trade")
public class Trade {

В нашем случае вы можете видеть из теста, что мы получаем следующее из / search / trade / 1

1
2
3
4
1

Как запустить и остановить Jetty Server

Я использую Jetty Plugin, чтобы запустить сервер и развернуть файл war, содержащий сервисы. http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin Сервер запускается со следующим фрагментом из pom.xml

1
2
3
4
5
6
7
<execution>
  <id>start-jetty</id>
  <phase>pre-integration-test</phase>
  <goals>
    <goal>run</goal>
  </goals>
</execution>

Сервер остановлен с помощью следующего фрагмента из файла pom.xml

1
2
3
4
5
6
7
<execution>
  <id>stop-jetty</id>
  <phase>post-integration-test</phase>
  <goals>
    <goal>stop</goal>
  </goals>
</execution>

Как запустить интеграционные тесты

Интеграционные тесты выполняются с использованием отказоустойчивых, как описано в оригинальной статье http://johndobie.blogspot.com/2011/06/seperating-maven-unit-integration-tests.html Мы используем новый Spring RestTemplate для упрощения вызова службы.

01
02
03
04
05
06
07
08
09
10
11
12
13
@Test
public void testGetTradeFromRestService() throws Exception {
  long id = 10L;
  createTrade(id);
  String tradeXml = new RestTemplate()
                          .getForObject(
                          "http://localhost:8080/find/trade/{id}",
                          String.class, id);
   
  System.out.println(tradeXml);
  Trade trade = getTradeFromXml(tradeXml);
  assertEquals(trade.getId(), id);
}

Ссылка: тестирование интеграции Maven и Spring Restful Services от нашего партнера JCG Джона Доби в блоге Agile Engineering Techniques .