Статьи

Использование Groovy & BeanBuilder для тестирования интеграции Spring + Hibernate

Уже достаточно широко известно, насколько хорош Groovy для написания модульных тестов для вашего кода Java. Благодаря наличию первоклассных конструкций для списков, карт и диапазонов в сочетании со встроенной поддержкой имитации с использованием Groovy для модульного тестирования — это отличный способ внедрить Groovy в кодовую базу Java.

Тем не менее, интеграционное тестирование — это совсем другое дело, и в случае Spring + Hibernate часто включает в себя сочетание баз данных в памяти, контейнеров Spring и так далее. Традиционный способ выполнить интеграционное тестирование в Spring — это расширить что-то вроде AbstractDependencyInjectionSpringContextTests, а затем переопределить метод getConfigLocations для предоставления статической конфигурации Spring XML для использования. Например, вот фрагмент из одного из тестов Grails, который использует эту технику:

     protected String[] getConfigLocations() {
return new String[] { "org/codehaus/groovy/grails/orm/hibernate/hibernate-mapped-class-tests.xml" };
}

 

Spring XML будет выполнять работу по настройке любых одноэлементных компонентов, от которых зависят ваши классы, таких как LocalSessionFactoryBean для Hibernate, источник данных в памяти с использованием чего-то вроде HSQLDB и так далее. Однако все это довольно утомительно, так как вам нужно поддерживать отдельный XML-файл из вашего теста и, что еще хуже, убедиться, что он находится в пути к классам в вашей сборке и в вашей IDE при запуске теста. Результат довольно болезненный.

Хорошая новость в том, что с Grails BeanBuilder есть лучший способ! Для тех из вас, кто не знает, BeanBuilder — это DSL для Spring, который позволяет вам создавать ApplicationContext на лету, используя Groovy-конструктор, такой как синтаксис. Мы собираемся использовать его для создания ApplicationContext для использования в интеграционном тесте с участием Hibernate.

Первое, что нам нужно сделать, это создать некоторые объекты Hibernate. Поскольку мы уже используем Groovy, мы можем также написать их на Groovy, так как синтаксис немного более краткий:

package com.mycompany
import javax.persistence.*
import org.hibernate.annotations.*

@Entity
@Table(name="faq_section")
class FaqSection
{
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
Long id

@Version
Long version

String title

@OneToMany(cascade = [javax.persistence.CascadeType.ALL], targetEntity = FaqElement.class)
@JoinColumn(name = "section_id", nullable = false)
@IndexColumn(name = "pos", base = 0)
List elements
}

@Entity
@Table(name="faq_element")
class FaqElement
{
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
Long id

@Version
Long version

String question
String answer

@ManyToOne
@JoinColumn(name = "section_id", nullable = false, updatable = false, insertable = false)
FaqSection section

}

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

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<!-- a SessionFactory instance listed as /jndi/name -->
<session-factory>
<!-- mapping files -->
<mapping class="com.mycompany.FaqSection" />
<mapping class="com.mycompany.FaqElement" />
</session-factory>

</hibernate-configuration>

Я сохранил их в файле faq-service-hiberate.cfg.xml, к которому мы можем обратиться позже, конечно, если вы используете правильный JPA, вы создадите файл persistence.xml и т. Д. В моем случае я использую прямой Hibernate. Теперь к интересному. Предполагая, что у нас есть некоторый вид FAQService, определенный как интерфейс с классом FAQServiceImpl, обеспечивающим реализацию для настройки и привязки всего этого в методе setUp нашего теста с использованием BeanBuilder, это так просто:

import grails.spring.BeanBuilder
import org.apache.commons.dbcp.BasicDataSource
import org.hibernate.cfg.AnnotationConfiguration
import org.hibernate.SessionFactory
import com.mycompany.FAQService
import com.mycompany.FAQServiceImpl

...

FAQService faqService

protected void setUp() {
def bb = new BeanBuilder()

bb.beans {
dataSource(BasicDataSource) {
url = "jdbc:hsqldb:mem:testDB"
driverClassName = "org.hsqldb.jdbcDriver"
username = "sa"
password = ""
}
sessionFactory(LocalSessionFactoryBean) {
dataSource = dataSource
configLocation = "classpath:com/mycompany/test/faq-service-hiberate.cfg.xml"
configurationClass = AnnotationConfiguration
hibernateProperties = ["hibernate.hbm2ddl.auto":"create-drop"]
}
faqService(FAQServiceImpl) {
sessionFactory = sessionFactory
}
}

def ctx = bb.createApplicationContext()

faqService = ctx.getBean("faqService")
}

So that’s pretty neat, whats going on here is I’m dynamically creating 3 beans: the dataSource, sessionFactory and faqService beans. These all get wired together with Spring and I can obtain a reference to the beans using the getBean method of the ApplicationContext.

So how could this be improved? The next step would be to write an abstract base class that constructed the ApplicationContext and then wired the beans into properties of the TestCase, so you could do something like:

 

class FaqServiceTests extends AbstractBeanBuilderDependencyInjectionTests {
def beans = {
// BeanBuilder code here
}

FAQSection faqSection

void testCreateFAQEntry() { }
}

Here it would read the «beans» property construct the ApplicationContext and wire the FAQSection bean into the test before calling the test method. But, I’ll save an example of this for a future episode 🙂

As for BeanBuilder itself, it is currently shipped as part of Grails, but can be used standalone by getting hold of the grails-spring-1.0.1.jar the latest version of which can be obtained here: http://dist.codehaus.org/grails

Enjoy!