• AbstractTestNGSpringContextTests 集成TestNg到 SpringTestContext框架
  • AbstractTransactionalTestNGSpringContextTests继承自AbstractTestNGSpringContextTests,不仅提供了事务的支持而且也提供了一些便捷的功能,如JDBC的访问

二、 在项目中添加如下依赖

  1. spring-context – we will be loading ApplicationContext.
  2. spring-test – to access spring’s testing framework.
  3. spring-jdbc – for JdbcTemplate and DataSource support.
  4. mysql-connector-java – MySql driver.
  5. 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>

三、TestNgSpring依赖注入

定义一个简单的Foobean,并注入实例,验证他的值

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
  • Beanfoo通过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
===============================================

四、TestNGSpring上下文

默认情况,Spring加载的context会被其进行缓冲,如果在加载bean花费一段时间,这样缓冲是可以提高性能的,使用@DirtiesContext可以覆盖这一默认行为,默认情况下,一个测试类中所有测试方法是相同的上下文,我们在方法上使用@DirtiesContext,可以标记这个方法重新缓冲上下文,可以使每个测试方法都独立隔离

示例方法介绍:

  • saveFooName 一个被注解@BeforeClass标记的方法,这个方法主要是保存foo的初始化name
  • removeFromCache 一个给@DirtiesContext注解的方法,因此这个上下文在缓冲中被标记成脏数据,同时注明了这个fooname属性被改变
  • verifyContextNew这个方法依赖于removeFromCache , 我们将检查foo's name是否默认的值还是改变后的值,因为removeFromCache方法被@DirtiesContext注解,被标记为脏数据,所以verifyContextNew将得到一个新的上下文,因此,这个foobean将被刷新,得到的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注入dataSourcetransactionManager

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
===============================================