Skip to content

Latest commit

 

History

History
198 lines (148 loc) · 7.45 KB

单元测试及事务.md

File metadata and controls

198 lines (148 loc) · 7.45 KB

如何使用Spring单元测试

在测试类上使用@RunWithContextConfiguration进行修饰,指定Spring的配置文件即可使用单元测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestClass {
    @Test
    public void test1() {}
}

如何在单元测试中支持事务

如何使用事务

在spring单元测试中,默认不支持事务,如果需要单元测试类支持事务,则需要继承AbstractTransactionalJUnit4SpringContextTests类,同时需要在配置文件中配置事务管理器。

xml配置:

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

单元测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestClass extends AbstractTransactionalJUnit4SpringContextTests {
    @Test
    public void test1() {}
}

为什么单元测试通过了,但是数据库的数据没有改变?

因为在spring单元测试中,默认会在方法的开始之前开启事务,当方法执行成功或失败以后回滚事务。

所有我们在单元测试方法中修改的数据可以在同一个方法中查询到,但是并不会体现在数据库上面。

如何使单元测试的事务提交

AbstractTransactionalJUnit4SpringContextTests的源码中有下面一段:

 * @see org.springframework.test.annotation.Commit
 * @see org.springframework.test.annotation.Rollback

我们查看CommitRollback注解的源码注释可以给出解释:

Commit:
	 * {@code @Commit} is a test annotation that is used to indicate that a
 	 * <em>test-managed transaction</em> should be <em>committed</em> after
 	 * the test method has completed.
Rollback:
	 * {@code @Rollback} is a test annotation that is used to indicate whether
	 * a <em>test-managed transaction</em> should be <em>rolled back</em> after
	 * the test method has completed.

上面是Spring官方给出的解释,简单的说就是,当在测试类或测试方法上标注@Commit或者@Rollback时,就会决定当前的事务是提交还是回滚。

看到这里,我们的解决方案就出来了,只需要在我们的测试类或者测试方法上标注@Commit或者@Rollback(false)就可以实现事务的提交。

事务源码解析

spring 在哪里进行检测注解的?

Spring单元测试实现事务管理主要类为TransactionalTestExecutionListener

Spring在当前类中进行开启事务,同时检测事务是否需要回滚。在该类中开始事务的方法为beforeTestMethod方法。

public class TransactionalTestExecutionListener extends AbstractTestExecutionListener {
    //...
    public void beforeTestMethod(final TestContext testContext) throws Exception {
    	// ...
        if (tm != null) {
            // 在此创建一个事务上下文,用于管理事务的提交或回滚
			txContext = new TransactionContext(testContext, tm, transactionAttribute, isRollback(testContext));
			runBeforeTransactionMethods(testContext);	// 运行事务之前的代码
			txContext.startTransaction();	// 开启事务
			TransactionContextHolder.setCurrentTransactionContext(txContext);
		}
        // ...
    }
    // ...
}

从上面的代码可以看出,Spring创建了一个TransactionContext用于管理事务的声明周期。同时在该方法中开启事务,并将事务绑定到当前单元测试的上下文上。

同时可以注意到isRollback()方法,Spring通过该方法检测单元测试类或方法上是否存在Rollback注解。

// 检测是否需要回滚
protected final boolean isRollback(TestContext testContext) throws Exception {
    	// 检测回滚测试是否是默认项
		boolean rollback = isDefaultRollback(testContext);
    	// 获取类或方法上的注解
		Rollback rollbackAnnotation =
				AnnotatedElementUtils.findMergedAnnotation(testContext.getTestMethod(), Rollback.class);
		if (rollbackAnnotation != null) {
            // 获取到覆盖默认策略
			boolean rollbackOverride = rollbackAnnotation.value();
			rollback = rollbackOverride;
		}
		return rollback;
	}
}

// 检测是否是默认的回滚策略
protected final boolean isDefaultRollback(TestContext testContext) throws Exception {
		Class<?> testClass = testContext.getTestClass();
    	// 获取类或方法上的Rollback注解
		Rollback rollback = AnnotatedElementUtils.findMergedAnnotation(testClass, Rollback.class);
		boolean rollbackPresent = (rollback != null);
		// ...
		if (rollbackPresent) {
            // Rollback 注解存在则返回注解内的 value 值
			boolean defaultRollback = rollback.value();
			return defaultRollback;
		}

		// else
		return txConfigAttributes.isDefaultRollback();
	}

isRollback()方法中首先检测事务回滚策略是否是默认策略。然后再次检测单元测试类或方法上是否存在Rollback注解,如果有注解,则覆盖默认的回滚策略。

spring在哪里进行事务的提交或回滚?

通过上面的注解检测阶段我们看到了一段代码:

txContext = new TransactionContext(testContext, tm, transactionAttribute, isRollback(testContext));

由此我们可以确定,事务的管理应该就是在当前类中进行管理的。下面我们看下当前调用的构造器:

TransactionContext(TestContext testContext,
                   PlatformTransactionManager transactionManager,
					TransactionDefinition transactionDefinition, boolean defaultRollback) {
	this.testContext = testContext;
	this.transactionManager = transactionManager;
	this.transactionDefinition = transactionDefinition;
	this.defaultRollback = defaultRollback;
	this.flaggedForRollback = defaultRollback;
}

对比构造器的调用和声明,可以看出检测Rollback注解获取到的值,在构造器中被赋值给flaggedForRollback属性。通过跟踪flaggedForRollback属性,在endTransaction()方法中可以看到如下代码:

void endTransaction() {
    if (this.flaggedForRollback) {
        this.transactionManager.rollback(this.transactionStatus);
    }
    else {
        this.transactionManager.commit(this.transactionStatus);
    }
}

至此,也就可以很容易的看出,为什么Rollback注解可以控制事务的提交或者回滚了。

Commit怎么没有被使用?

在前面的源码解析中检测和获取的注解值都是Rollback,那Commit就没有作用了吗?

带着这个疑问,我们可以看下Commit注解的声明:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Rollback(false)
public @interface Commit {
}

看到源码豁然开朗,原来Commit只是一个标记注解,它最主要的作用就是承载@Rollback(false)

这样在注解搜索阶段就可以通过获取类或方法上的所有注解,同时获取注解的注解,也就是说,spring通过检测类或者方法上的所有注解,同时检测获取到的注解有没有被Rollback注解进行修饰。通过这种方法,在注解检测阶段就不需要分别对这两类注解分别进行解析。