写了一个月的单元测试,总算明白大学里这门课白学了
上大学的时候学过软件测试这门课,但是在公司实习时才真正实战了一把。先花了半个月把Junit In Action 英文版看完了(话说在公司学习效率就是比学校里高啊) 废话不多说,直接开始主题。
为什么要写单元测试?
两个理由:1.给我们重构的信心(give us the confidence to refactor)。一堆纠缠而无测试的代码你敢随便修改?
2.好的单元测试就是文档(documenting expected behavior)。几个实用的例子比文档让人感兴趣的多。
概念就不说了,黑盒白盒TDD,语句路径覆盖率,网上都有,直接讲讲我的收获吧。
我在项目里最初就是给一些基础类(EntityModel)写测试,它是一个抽象类,内部保存Entity,并执行增删改查的操作。那么我第一个遇到的问题就是:如何测试一个抽象类?方法很简单,就是去定义一个子类,然后生成子类的实例做测试。拿测试update函数来说,有三个步骤:
1.准备一个Entity,给他设置两个属性,age = 10,weight = 50。
2.把它add进EntityModel,进行update操作,修改他的weight = 80。
3.最后get出来,验证得到的Entity weight = 80 且 age = 10。
代码如下:
@Test public void testUpdateEntityWhenNotTheSameProperty() { MyEntity myEntity = createMyEntity(AGE_ID, WEIGHT_ID, 0L); entityModel.addEntity(myEntity); entityModel.updateEntity(myEntity.getClass(), myEntity.getId(), MyEntity.PROPERTY_NAME_AGE, DIFF_AGE_ID); assertEquals(DIFF_AGE_ID, myEntity.getAge()); }
而这三个步骤总结起来就是"准备-构建-验证"(《clean code》 , Unit Test chapter),测试用例都遵从这个步骤。
写着写着,你会遇到一些问题。怎么验证我的函数里面调用了一个静态方法去记录log日志?怎么验证一个私有方法?怎么测试一个接口?怎么测试向一个数据库插入数据的操作?怎么验证我测试的程序一定会抛出一个异常?……这下就要到网上去查了,有许多现成的框架可供使用,大名鼎鼎的Junit;Mockito可以模拟接口,模拟函数返回值,验证方法调用次数;Powermock(查看需FQ)可以模拟静态方法和final方法;Java的反射机制可以模拟私有方法。
给函数起名也有讲究。我一般用test__when__thenReturn__的结构,尽量让看的人不用看代码就能知道功能。
不能为了省事而把不同性质的验证放在一个test中,一次只做一件事,做好一件事。
而且最重要的是,在写单元测试过程中,我慢慢就对代码的逻辑清楚了。如果必须要覆盖每个判断,等我写完测试用例,我就已经知道这个函数的每个细节。这对于以后重构很有帮助。
写的不多,但是我要坚持写下去,因为总结和思考的过程就是成长。
——周五晚在公司
--------------------------------------------
今天又学到一招,mock过静态方法的朋友知道,需要在类前添加
@RunWith(PowerMockRunner.class)
@PrepareForTest(xxx.class)
而如果我需要在一个类中验证两个不同类的静态方法,只需要
@PrepareForTest({Hello.class, World.class})
或是
@PrepareForTest(fullyQualifiedNames = {"com.util.log4j.LogManager","com.rcm.model.risk.defs.BettingFamilies"})
——2015/2/2