-
AbstractTestNGSpringContextTests
集成TestNg
到 SpringTestContext
框架 -
AbstractTransactionalTestNGSpringContextTests
继承自AbstractTestNGSpringContextTests
,不仅提供了事务的支持而且也提供了一些便捷的功能,如JDBC
的访问
二、 在项目中添加如下依赖
-
spring-context
– we will be loadingApplicationContext.
-
spring-test
– to access spring’s testing framework. -
spring-jdbc
– forJdbcTemplate
andDataSource
support. -
mysql-connector-java
– MySql driver. -
testng
– as this is our testing tool.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.javacodegeeks.testng.spring</groupId>
<artifactId>testNGSpring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.8</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<spring.version>4.1.5.RELEASE</spring.version>
</properties>
</project>
三、TestNg
和Spring
依赖注入
定义一个简单的Foo
bean,并注入实例,验证他的值
Foo:
public class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
注入该实例,在context.xml
context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean id="foo" class="com.kcwl.testng.spring.Foo">
<property name="name" value="TestNG Spring"/>
</bean>
</beans>
这个实例通过Spring
依赖注入初始化测试实例
- 这个类通过继承
AbstractTestNGSpringContextTests
确保实例可以注入到我们的测试类中 - 上下文通过注解
@ContextConfiguration
value值是context.xml
,Spring
将使用这个文件,这是上下文applicationContext
- Bean
foo
通过Autowired
注入,同时注入BeanFactory
测试方法:
-
verifyFooName
验证foo
是否被注入,并且注入的name是否一致 -
verifyBeanFactory
验证bean
工厂是否被注入
*SpringTestNGDependencyInjectionExample:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import static org.testng.Assert.*;
import org.testng.annotations.Test;
@ContextConfiguration("context.xml")
public class SpringTestNGDependencyInjectionExample extends AbstractTestNGSpringContextTests {
@Autowired
private BeanFactory beanFactory;
@Autowired
private Foo foo;
@Test
public void verifyFooName() {
System.out.println("verifyFooName: Is foo not null? " + (foo != null));
assertNotNull(foo);
System.out.println("verifyFooName: Foo name is '" + foo.getName() + "'");
assertEquals(foo.getName(), "TestNG Spring");
}
@Test
public void verifyBeanFactory() {
System.out.println("verifyBeanFactory: Is bean factory not null? " + (beanFactory!= null));
assertNotNull(beanFactory);
}
}
testng.xml:
<?xml version="1.0" encoding="UTF-8"?>
<suite name="TestNGSpringIntegration Suite" parallel="false">
<test name="TestNGSpringIntegrationTest">
<classes>
<class name="com.kcwl.testng.spring.SpringTestNGDependencyInjectionExample"/>
</classes>
</test>
</suite>
Output:
[TestNG] Running:
C:\kcwl\testNGSpring\src\test\resources\com\kcwl\testng\spring\testng_context_dependency_injection.xml
verifyBeanFactory: Is bean factory not null? true
verifyFooName: Is foo not null? true
verifyFooName: Foo name is 'TestNG Spring'
===============================================
TestNGSpringIntegration Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================
四、TestNG
和Spring
上下文
默认情况,Spring加载的context
会被其进行缓冲,如果在加载bean
花费一段时间,这样缓冲是可以提高性能的,使用@DirtiesContext
可以覆盖这一默认行为,默认情况下,一个测试类中所有测试方法是相同的上下文,我们在方法上使用@DirtiesContext
,可以标记这个方法重新缓冲上下文,可以使每个测试方法都独立隔离
示例方法介绍:
saveFooName
一个被注解@BeforeClass
标记的方法,这个方法主要是保存foo
的初始化name
removeFromCache
一个给@DirtiesContext
注解的方法,因此这个上下文在缓冲中被标记成脏数据,同时注明了这个foo
的name
属性被改变verifyContextNew
这个方法依赖于removeFromCache
, 我们将检查foo's name
是否默认的值还是改变后的值,因为removeFromCache
方法被@DirtiesContext
注解,被标记为脏数据,所以verifyContextNew
将得到一个新的上下文,因此,这个foo
bean将被刷新,得到的name
仍然是默认值verifyContextSame
这个方法依赖于verifyContextNew
,这个方法并没有被@DirtiesContext
注解,所有verifyContextSame
将得到一个缓冲的上下文
SpringTestNGContextCacheExample:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@ContextConfiguration("context.xml")
public class SpringTestNGContextCacheExample extends AbstractTestNGSpringContextTests {
private String TestNG_Spring;
private static final String MODIFIED_FOO_NAME = "TestNG Spring Name Changed";
private ApplicationContext dirtiedApplicationContext;;
@Autowired
private Foo foo;
@BeforeClass
private void saveFooName() {
TestNG_Spring= foo.getName();
System.out.println("BeforeClass: foo name is '" + TestNG_Spring + "'");
assertEquals(TestNG_Spring, "TestNG Spring");
}
@Test
@DirtiesContext
public void removeFromCache() {
String newFooName = "New foo name";
foo.setName(newFooName);
System.out.println("removeFromCache: foo name changed to '" + foo.getName() + "'");
this.dirtiedApplicationContext = super.applicationContext;
System.out.println("removeFromCache: annotated @DirtiesContext so context will be marked for removal in afterMethod ");
}
@Test(dependsOnMethods = {"removeFromCache"})
public void verifyContextNew() {
System.out.println("verifyContextNew: is context re-cached? " + (dirtiedApplicationContext != applicationContext));
System.out.println("verifyContextNew: foo name is '" + foo.getName() + "'");
assertNotSame(super.applicationContext, this.dirtiedApplicationContext,
"The application context should have been 'dirtied'.");
assertEquals(foo.getName(), TestNG_Spring);
this.dirtiedApplicationContext = super.applicationContext;
foo.setName(MODIFIED_FOO_NAME);
System.out.println("verifyContextNew: modify foo name to '" + MODIFIED_FOO_NAME + "'");
}
@Test(dependsOnMethods = { "verifyContextNew" })
public void verifyContextSame() {
System.out.println("verifyContextSame: is context cached? " + (dirtiedApplicationContext == applicationContext));
assertSame(this.applicationContext, this.dirtiedApplicationContext,
"The application context should NOT have been 'dirtied'.");
System.out.println("verifyContextSame: foo name is '" + foo.getName() + "'");
assertEquals(foo.getName(), MODIFIED_FOO_NAME);
}
}
testng.xml:
<?xml version="1.0" encoding="UTF-8"?>
<suite name="TestNGSpringIntegration Suite" parallel="false">
<test name="TestNGSpringIntegrationTest">
<classes>
<class name="com.javacodegeeks.testng.spring.SpringTestNGContextCacheExample"/>
</classes>
</test>
</suite>
·Output:
[TestNG] Running:
C:\javacodegeeks_ws\testNGSpring\src\test\resources\com\javacodegeeks\testng\spring\testng_context_cache.xml
BeforeClass: foo name is 'TestNG Spring'
removeFromCache: foo name changed to 'New foo name'
removeFromCache: annotated @DirtiesContext so context will be marked for removal in afterMethod
verifyContextNew: is context re-cached? true
verifyContextNew: foo name is 'TestNG Spring'
verifyContextNew: modify foo name to 'TestNG Spring Name Changed'
verifyContextSame: is context cached? true
verifyContextSame: foo name is 'TestNG Spring Name Changed'
===============================================
TestNGSpringIntegration Suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================
五、TestNg
与Spring 事务管理
默认情况,测试方法执行完成后事务将回滚,我们可以覆盖这个默认行为提交事务。该实例需要数据库支持,我们可以使用Mysql
或者H2
内存数据库进行测试
创建employee
表,字段名称name
db-schema.sql:
drop table if exists `employee`;
CREATE TABLE employee (
name VARCHAR(20) NOT NULL,
PRIMARY KEY(name)
);
添加数据
data.sql:
INSERT INTO employee VALUES('Joe');
INSERT INTO employee VALUES('Sam');
additional_data.sql:
INSERT INTO employee VALUES('John');
通过tran_context.xml
注入dataSource
和transactionManager
tran_context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
<jdbc:initialize-database data-source="dataSource"
enabled="true">
<jdbc:script location="classpath:com/javacodegeeks/testng/spring/db-schema.sql" />
</jdbc:initialize-database>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/test" />
<property name="username" value="root" />
<property name="password" value="mnrpass" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" autowire="byName"/>
</beans>
关于这个测试类
- 这个测试类继承
AbstractTransactionalTestNGSpringContextTests
,使这个类自动地运行带有事务 saveMethodName
捕获每次测试运行的方法,之后会被这个方法进行断言tran
断言JdbcTemplate
是否被注入beforeTransaction
- 被@BeforeTransaction
注解,在开始事务的时候执行,删除表中的所有数据,并重新创建数据insertEmployeeAndCommit
插入新数据,并显示的提交事务insertEmployeeWithRollbackAsDefault
插入新数据,默认的行为是方法执行后事务进行回滚,因此我们在@AfterTransaction
方法中看不到插入的新数据insertEmployeeWithCommitAsDefault
,覆盖默认行为通过@Rollback(false)
,因此事务将被自动提交insertEmployeeUsingSqlAnnotation
使用注解@Sql
,执行插入数据afterTransaction
事务执行完的执行方法,断言所有的执行结果
*SpringTestNGTransactionExample:
package com.kcwl.testng.spring;
import static org.springframework.test.context.transaction.TestTransaction.end;
import static org.springframework.test.context.transaction.TestTransaction.flagForCommit;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@ContextConfiguration("tran_context.xml")
public class SpringTestNGTransactionExample extends AbstractTransactionalTestNGSpringContextTests {
private String method;
@Autowired
private JdbcTemplate jdbcTemplate;
@BeforeMethod
public void saveMethodName(Method method) {
this.method = method.getName();
}
@Test
public void tran() {
System.out.println("tran: verify JdbcTemplate is not null");
assertNotNull(jdbcTemplate);
}
@BeforeTransaction
public void beforeTransaction() {
System.out.println("before transaction starts, delete all employees and re-run employee script");
deleteFromTables("employee");
executeSqlScript("classpath:/com/javacodegeeks/testng/spring/data.sql", false);
}
@Test
public void insertEmployeeAndCommit() {
System.out.println("insertEmployeeAndCommit: insert employee 'Bill' and commit");
String emp = "Bill";
jdbcTemplate.update("insert into employee(name) values (?)", emp);
assertEquals(countRowsInTableWhere("employee", "name='" + emp + "'"), 1);
flagForCommit();
end();
}
@Test
public void insertEmployeeWithRollbackAsDefault() {
System.out.println("insertEmployeeWithRollbackAsDefault: insert employee 'Bill', rollback by default");
String emp = "Bill";
jdbcTemplate.update("insert into employee(name) values (?)", emp);
assertEquals(countRowsInTableWhere("employee", "name='" + emp + "'"), 1);
}
@Test
@Rollback(false)
public void insertEmployeeWithCommitAsDefault() {
System.out.println("insertEmployeeWithCommitAsDefault: insert employee 'Bill', commit by default");
String emp = "Bill";
jdbcTemplate.update("insert into employee(name) values (?)", emp);
assertEquals(countRowsInTableWhere("employee", "name='" + emp + "'"), 1);
}
@Test
@Sql({"additional_data.sql"})
public void insertEmployeeUsingSqlAnnotation() {
System.out.println("insertEmployeeUsingSqlAnnotation: run additional sql using @sql annotation, rollback by default");
assertEquals(countRowsInTableWhere("employee", "name='John'"), 1);
}
@AfterTransaction
public void afterTransaction() {
switch (method) {
case "insertEmployeeAndCommit":
assertEmployees("Bill", "Joe", "Sam");
System.out.println("insertEmployeeAndCommit: employees found: 'Bill', 'Joe' and 'Sam'");
break;
case "insertEmployeeWithRollbackAsDefault":
System.out.println("insertEmployeeWithRollbackAsDefault: employees found: 'Joe' and 'Sam'");
assertEmployees("Joe", "Sam");
break;
case "insertEmployeeWithCommitAsDefault":
System.out.println("insertEmployeeWithCommitAsDefault: employees found: 'Bill', 'Joe' and 'Sam'");
assertEmployees("Bill", "Joe", "Sam");
break;
case "tran":
break;
case "insertEmployeeUsingSqlAnnotation":
System.out.println("insertEmployeeWithRollbackAsDefault: employees found: 'Joe' and 'Sam'");
assertEmployees("Joe", "Sam");
break;
default:
throw new RuntimeException(
"missing 'after transaction' assertion for test method: "
+ method);
}
}
private void assertEmployees(String... users) {
List expected = Arrays.asList(users);
Collections.sort(expected);
List actual = jdbcTemplate.queryForList("select name from employee", String.class);
Collections.sort(actual);
System.out.println("Employees found: " + actual);
assertEquals(expected, actual);
}
}
testng.xml:
<?xml version="1.0" encoding="UTF-8"?>
<suite name="TestNGSpringIntegration Suite" parallel="false">
<test name="TestNGSpringIntegrationTest">
<classes>
<class name="com.javacodegeeks.testng.spring.SpringTestNGTransactionExample"/>
</classes>
</test>
</suite>
Output:
[TestNG] Running:
C:\javacodegeeks_ws\testNGSpring\src\test\resources\com\javacodegeeks\testng\spring\testng_spring_transaction.xml
before transaction starts, delete all employees and re-run employee script
insertEmployeeAndCommit: insert employee 'Bill' and commit
Employees found: [Bill, Joe, Sam]
insertEmployeeAndCommit: employees found: 'Bill', 'Joe' and 'Sam'
before transaction starts, delete all employees and re-run employee script
insertEmployeeUsingSqlAnnotation: run additional sql using @sql annotation, rollback by default
insertEmployeeWithRollbackAsDefault: employees found: 'Joe' and 'Sam'
Employees found: [Joe, Sam]
before transaction starts, delete all employees and re-run employee script
insertEmployeeWithCommitAsDefault: insert employee 'Bill', commit by default
insertEmployeeWithCommitAsDefault: employees found: 'Bill', 'Joe' and 'Sam'
Employees found: [Bill, Joe, Sam]
before transaction starts, delete all employees and re-run employee script
insertEmployeeWithRollbackAsDefault: insert employee 'Bill', rollback by default
insertEmployeeWithRollbackAsDefault: employees found: 'Joe' and 'Sam'
Employees found: [Joe, Sam]
before transaction starts, delete all employees and re-run employee script
===============================================
TestNGSpringIntegration Suite
Total tests run: 5, Failures: 0, Skips: 0
===============================================