在之前的一篇用spring-test、dbunit、flyway测试Java持久层中,我曾介绍过使用spring-test-dbunit提供的@DatabaseSetup和@ExpectedDatabase简化测试断言的方法,但使用这种办法也有痛点:
<!--order_save_fixture.xml,这里定义“原型数据”-->
<dataset>
<t_order tracking_id="1" status="WAIT_PAYMENT"/>
<t_order_item tracking_id="1" name="item1" quantity="1"/>
<t_order_item tracking_id="1" name="item2" quantity="2"/>
</dataset>
<!--order_save_expected.xml,至少需要额外定义一遍原型数据,哪怕它们不会变化-->
<dataset>
<t_order tracking_id="1" status="WAIT_PAYMENT"/>
<t_order_item tracking_id="1" name="item1" quantity="1"/>
<t_order_item tracking_id="1" name="item2" quantity="2"/>
<t_order tracking_id="2" status="WAIT_PAYMENT"/>
<t_order_item tracking_id="2" name="item1" quantity="1"/>
<t_order_item tracking_id="2" name="item2" quantity="2"/>
</dataset>
优秀的测试会尽可能地直接突出影响测试结果的关键数据,它们越是醒目, 测试就容易被他人理解、越容易维护。而现在我们却还需要编写重复的数据,这可不行。 因此,我为spring-test-dbunit提供了一个小扩展, 使得setup和expect数据可以定义在同一个文件中(省去了切换的麻烦),而且没有重复数据!
<!--order_save.xml-->
<dataset>
<given>
<t_order tracking_id="1" status="WAIT_PAYMENT"/>
<t_order_item tracking_id="1" name="item1" quantity="1"/>
<t_order_item tracking_id="1" name="item2" quantity="2"/>
<given/>
<then>
<added>
<t_order tracking_id="2" status="WAIT_PAYMENT"/>
<t_order_item tracking_id="2" name="item1" quantity="1"/>
<t_order_item tracking_id="2" name="item2" quantity="2"/>
</added>
</then>
</dataset>
目前,需要对测试用例进行三处改动来支持这个新的数据格式,请注意以下代码的注释:
//other annotations
@DbUnitConfiguration(dataSetLoader = GivenWhenThenFlatXmlDataSetLoader.class) //use this loader
public class HibernateOrderRepositoryTest {
@DatabaseSetup("given:classpath:order_save.xml") //decorate xml file with "given:" prefix
@ExpectedDatabase(value = "then:classpath:order_save.xml",//decorate xml file with "then:" prefix
assertionMode = NON_STRICT_UNORDERED)
@Test public void should_saves_order() throws Exception {
final String trackingIdOfPrototype = "1";
final String trackingIdOfToBeSaved = "2";
final Order toBeSaved = clone(trackingIdOfPrototype, trackingIdOfToBeSaved);
subject.store(toBeSaved);
}
// other code hide for brevity
}
除了added(对应insert)之外,GivenWhenThenFlatXmlDataSetLoader还支持delete声明:
<!--order_remove_items.xml-->
<dataset>
<given>
<t_order tracking_id="1" status="WAIT_PAYMENT"/>
<t_order_item tracking_id="1" name="item1" quantity="1"/>
<t_order_item tracking_id="1" name="item2" quantity="2"/>
<given/>
<then>
<deleted>
<t_order_item tracking_id="2" name="item2"/>
</deleted>
</then>
</dataset>
你只需要把唯一标识要删除的数据的字段列出来,GivenWhenThenFlatXmlDataSetLoader会智能地找到它。 GivenWhenThenFlatXmlDataSetLoader还支持modify声明,用来支持测试update的用例,同样地, 你只需要列出有变化的字段,但遗憾的是,目前还无法为modified智能找到主键,你需要显示地告诉它 :-(
<!--order_remove_items.xml-->
<dataset>
<given>
<t_order tracking_id="1" status="WAIT_PAYMENT"/>
<t_order_item tracking_id="1" name="item1" quantity="1"/>
<t_order_item tracking_id="1" name="item2" quantity="2"/>
<given/>
<then>
<modified pk="tracking_id, name">
<t_order_item tracking_id="2" name="item2" quantity="3"/>
</modified>
<!--如果有多张表,可以定义多个<modified/>-->
</then>
</dataset>
更详细的例子,可以参考Github上的说明. 如果你有改进的好点子,不妨告诉我或是干脆来个pull request :-)