最近组里新上了一个项目,使用了Spring框架,之前一直用python的Django框架的我看着满屏的annotation简直傻了眼。在简单上手之后,其实发现annotation一定程度上让代码变的简洁了许多,但前提是得弄懂annotation的意义是什么。
项目是一个自动化债务分发系统,架构上设计成了一个state machine的结构。在设计状态转化时,组里决定用eventbus来做内部状态转化的工具,即状态A在完成后会调用eventPublisher发送一个事件,subscribe那个事件的进程会自动收取事件运行下一个模块的程序。这样做的主要好处是把一个大的系统解藕分成了数个较小的模块,这样在开发过程中会减少不同模块之间的依赖,只需模块发送的事件中包含必要的信息即可。
但使用eventbus也带来了一定的麻烦,一贯使用DataJpaTest写测试用例的我一下子不知道如何下手来测试eventbus的行为。最基本的想法是发送一个事件触发事件处理器,然后进行处理后发送下一个模块监听的事件,但为了避免出发下一个事件处理器,事件发送器应该被mock。由于Spring框架有Dependency Injection的特性,所有的class都尽可能的封装成了java bean的形式,而且由于eventbus把事件的发送和接收解藕,在测试中也不会构造事件处理器的对象,这就导致mock后的对象不能传入到事件处理器中。
@Component
@RequiredArgsConstructor
@Transactional
public class ClaimEnrichedHandler {
@Autowired
@Qualifier(INCOMING)
private final EventSource eventSource;
@Autowired
@Qualifier(OUTGOING)
private final EventPublisher eventPublisher;
@PostConstruct
void postConstruct() {
eventSource.register(this);
}
@Subscribe
@SuppressWarnings("unused")
public void handleEvent(ClaimEnrichedEvent event) {
LOG.info("New receiving event: {}", event);
LOG.info("New posting event: {}", claimAssignedEvent);
}
为了将mock的对象传入事件处理其中,最好的办法还是避免使用DataJpaTest或者其他spring框架提供的方法,这样其实也减少了与数据库的接触,因为DataJpaTest 会在内存中建立临时的数据库以供测试。下面的方法使用了Mockito来完成测试:
@VerifyNoMoreInteractions
public class ClaimEnrichedHandlerTest {
private ThrowingEventBus eventSource;
@Mock
private EventPublisher eventPublisher;
@Mock
private Process process;
private Handler handler;
@BeforeEach
void setup() {
eventSource = new ThrowingEventBus();
handler = new Handler(eventSource, eventPublisher, process);
handler.postConstruct();
}
@Test
void claimEnrichedHandlerTest() {
// Given
data = prepare_content();
TriggerEvent triggerEvent = TriggerEvent
.builder()
.data(data)
.build();
// When
eventSource.post(triggerEvent);
// Then
verify(eventPublisher).post(any(NewEvent.class));
}
最上方的@VerifyNoMoreInteractions会验证每处mock对象所做的交互,如果漏在验证的部分漏写了验证会报错。
因为这个测试没有使用Spring框架,每个类都必须在setup中建立新的对象,而@BeforeEach标注的方法会在调用测试用例之前调用。而@Mock标注的对象会创建为对应类的mock。这样导入到Handler中就能成功mock掉eventPublisher了,verify会在eventPublisher调用post方法且传入一个NewEvent之后被唤醒然后验证。
这样看了Spring还是做了很多事情的,但在某些情况下确实会由于过度简化而使得一些简单的逻辑变得复杂。这种情况下,清楚理解Spring为用户做了什么就显得尤为重要了,这样才能让框架的使用更加灵活。