小插曲、测试版的为什么组合优于继承
你可能已经见过各种版本的组合优于继承的理论和实例,下面我贡献一个测试版本的 :D
先来看一下template版本的测试方案,我们需要分别对SubTemplate1和SubTemplate2的template()编写测试,并且由于AbstractTemplate的template()依赖SomeDependency和AnotherDependency,所以在以上两个测试中,我们需要使用Stub/Mock将它们替换掉,并为每一个测试准备一整套测试数据,即使它们的implementThis()方法只是return 1 和return 2。
再来看一下strategy版本的测试方案,我们需要为UsingStrategy编写测试,并用Stub/Mock替换Strategy、SomeDependency和AnotherDependency的实现,再分别为Strategy1、Strategy2编写测试。
你看,是不是strategy版本的测试方案的职责更清晰一些,每个测试都只负责被测试目标自己的代码,这种感觉在Template的子类特别多,Template包含的模板代码越是完整的时候越明显。
三、样板代码
有些代码吧,不管开发什么功能都要写的,比如经典的 service-dao,我们来看两个案例:
Java代码
@Transactional (1) @Override public void cancel(String orderId) { Order order = orderRepository.findBy(orderId); (2) order.cancel(); orderRepository.store(order); (3) }
@Transactional (4) @Override public void acknowledge(String orderId) { Order order = orderRepository.findBy(orderId); (5) order.acknowledge(); orderRepository.store(order); (6) } |
这段代码实现了两个功能,取消订单和确认订单,我们假设其逻辑非常简单,Order对象自己就可以处理,其中(2)、(5) 和 (3)、(6)是配对出现的,由于需要持久化数据,这两步总是免不了的。另外,比较隐蔽的是(1)、(4),这两行代码声明了事务,但很少有人去测试它们,而且经常会忘记添加它们。
Java代码
@Test public void orderIsCanceledAfterCancelling() throws Exception { final Order order = new OrderFixture().build(); final String orderId = order.getId(); context.checking(new Expectations() { { allowing(orderRepository).findBy(orderId); will(returnValue(order)); oneOf(orderRepository).store(order); } }); target.cancel(orderId); assertTrue(order.isCanceled()); }
@Test public void orderIsAcknowledgedAfterAcknowledging() throws Exception { final Order order = new OrderFixture().build(); final String orderId = order.getId(); context.checking(new Expectations() { { allowing(orderRepository).findBy(orderId); will(returnValue(order)); oneOf(orderRepository).store(order); } }); target.acknowledge(orderId); assertTrue(order.isAcknowledged()); } |