最近在公司做需求,要求开发需要有相应的单元测试代码,第一次做单测相关的知识,就在这做一篇总结
一、JUnit
JUnit是Java最基础的测试框架,主要的作用就是断言。
方法名 | 方法描述 |
---|---|
assertEquals | 断言传入的预期值与实际值是相等的 |
assertNotEquals | 断言传入的预期值与实际值是不相等的 |
assertArrayEquals | 断言传入的预期数组与实际数组是相等的 |
assertNotNull | 断言传入的对象是不为空 |
assertFalse | 断言条件为假 |
assertSame | 断言两个对象引用同一个对象,相当于“==” |
assertThat | 断言实际值是否满足指定的条件 |
常用注解
注解名 | 含义 |
---|---|
@Test | 表示此方法为测试方法 |
@Before | 在每个测试方法前执行,可做初始化操作 |
@After | 在每个测试方法后执行,可做释放资源操作 |
@BeforeClass | 在类中所有方法前运行。此注解修饰的方法必须是static void |
@Parameters | 指定测试类的测试数据集合 |
@FixMethodOrder | 指定测试类中方法的执行顺序 |
@RunWith | 指定该测试类使用某个运行器 |
JUnit常用方法
我们测试下面这个简单的时间转换工具类,来说明一下具体的用法。
class DateUtil {
companion object {
// 英文全称 如:2017-11-01 22:11:00val FORMAT_YMDHMS = "yyyy-MM-dd HH:mm:ss"/*** 掉此方法输入所要转换的时间输入例如("2017-11-01 22:11:00")返回时间戳** @param time* @return 时间戳*/@Throws(ParseException::class)fun dateToStamp(time: String): Long {
val sdr = SimpleDateFormat(FORMAT_YMDHMS, Locale.CHINA)val date = sdr.parse(time)return date.time}/*** 将时间戳转换为时间*/fun stampToDate(lt: Long): String {
val simpleDateFormat = SimpleDateFormat(FORMAT_YMDHMS, Locale.CHINA)val date = Date(lt)return simpleDateFormat.format(date)}}
}
我们发现预期值和实际结果不符合,测试失败
接下来测试dateToStamp方法,这里写两个断言方法
左下角这个绿色的勾勾就表示运行成功啦
测试异常
比如上述的dateToStamp()方法中,可能会抛出ParseException异常,我们可以验证是否抛出
验证一个方法是否抛出了异常,可以给@Test注解设置expected参数来实现
参数化测试
连续执行三次都成功(这里需要注意,每一次执行都是独立的,相互不影响)
二、Mockito
在实际的单元测试中,我们测试的类之间会有或多或少的耦合,导致我们无法顺利的进行测试,这时我们就可以使用Mockito,Mockito库能够Mock(我喜欢理解为模拟)对象,替换我们原先依赖的真实对象,这样我们就可以避免外部的影响,只测试本类,得到更准确的结果。
常用的Mock方式
class MockitoTest {
/*** 方法1*/@Testfun testIsNotNull() {
val person1 = mock(Person::class.java)Assert.assertNotNull(person1)}/*** 方法2 注解方法*/@Mocklateinit var person2: Person@Beforefun setUp() {
MockitoAnnotations.initMocks(this)}@Testfun testIsNotNull2() {
Assert.assertNotNull(person2)}/*** 方法3 MockitoRule*/@Mocklateinit var person3: Person@Rulepublic var mockitoRule = MockitoJUnit.rule()@Testfun testIsNotNull3() {
Assert.assertNotNull(person3)}}
常用打桩方法
因为Mock出的对象中非void方法都将返回默认值,比如int方法将返回0,对象方法将返回null等,而void方法将什么都不做。“打桩”顾名思义就是将我们Mock出的对象进行操作,比如提供模拟的返回值等,给Mock打基础。
方法名 | 方法描述 |
---|---|
thenReturn(T value) | 设置要返回的值 |
thenThrow(Throwable… throwables) | 设置要抛出的异常 |
thenAnswer(Answer<?> answer) | 对结果进行拦截 |
doReturn(Object toBeReturned) | 提前设置要返回的值 |
doCallRealMethod() | 调用某一个方法的真实实现 |
doNothing() | 设置void方法什么也不做 |
举例
open class Person constructor(name: String, sex: String) {
val mName: Stringval mSex: Stringinit {
mName = name;mSex = sex}open fun getName(): String {
return mName}open fun getSex(): String {
return mSex}open fun eat(something: String): String {
return "吃什么吐什么"}
}
成功返回小明并且抛出异常
thenAnswer——对方法进行拦截
行为测试
前面所说的都是状态测试,但是如果不关心返回结果,而是关心方法有否被正确的参数调用过,这时候就应该使用验证方法了。从概念上讲,就是和状态测试所不同的“行为测试”了
verify(T mock)验证发生的某些行为
方法名 | 方法描述 |
---|---|
after(long millis) | 在给定的时间后进行验证 |
timeout(long millis) | 验证方法执行是否超时 |
atLeast(int minNumberOfInvocations) | 至少进行n次验证 |
atMost(int maxNumberOfInvocations) | 至多进行n次验证 |
times(int wantedNumberOfInvocations) | 验证调用方法的次数 |
only() | 验证方法只被调用一次,相当于times(1) |
@Testfun testPersonVerifyAfter() {
Mockito.`when`(person2.getName()).thenReturn("jack")// 延时一秒验证println(person2.getName())println(System.currentTimeMillis())verify(person2, after(1000)).getName()println(System.currentTimeMillis())}@Testfun testPersonVerifyAtLeast() {
person2.getSex()person2.getSex()// 至少验证2次verify(person2, atLeast(2)).getSex()// 该方法验证了两次verify(person2, times(2)).getSex()}// 常用参数匹配器@Testfun testPersonAny() {
`when`(person2.eat(ArgumentMatchers.anyString())).thenReturn("一日三餐很固定")println(person2.eat("很固定?"))`when`(person2.eat(ArgumentMatchers.contains("面"))).thenReturn("面食")println(person2.eat("刀削面"))}
验证执行顺序
将最后的
spy
Mockito框架不支持mock匿名类、final类、static方法、private方法。而PowerMock框架解决了这些问题。
三、PowerMock
package com.example.testdateunitabstract class Fruit {
private val fruit = "水果"open fun getFruit(): String {
return fruit}
}open class Banana: Fruit() {
companion object {
private val COLOR = "黄色的"@JvmStaticfun getColor(): String {
return COLOR}}private fun flavor(): String {
return "甜甜的"}fun getBananaInfo(): String {
return flavor() + getColor()}fun isLike(): Boolean {
return true}}
@PrepareForTest(Banana.class)
@RunWith(PowerMockRunner.class)
public class BananaTest {
@Testpublic void testMethod() throws Exception {
Banana banana = PowerMockito.spy(new Banana());// 返回正常的值System.out.println(banana.getBananaInfo());// mock私有方法PowerMockito.when(banana, "flavor").thenReturn("不甜了");System.out.println(banana.getBananaInfo());// 修改静态常量Whitebox.setInternalState(Banana.class, "COLOR", "红色");System.out.println(Banana.getColor());// mock静态方法PowerMockito.mockStatic(Banana.class);PowerMockito.when(Banana.getColor()).thenReturn("绿色");System.out.println(Banana.getColor());// mock夫类私有变量MemberModifier.field(Banana.class, "fruit").set(banana, "蔬菜");System.out.println(banana.getFruit());// mock fina方法PowerMockito.when(banana.isLike()).thenReturn(true);}
}
上面我们有说到使用PowerMock就必须加@RunWith(PowerMockRunner.class),但是我们毕竟有时会使用多个测试框架,可能@RunWith会占用。这时我们可以使用@Rule。代码如下:
@Rule
public PowerMockRule rule = new PowerMockRule();/*** 这种方法需要加入依赖* testCompile "org.powermock:powermock-module-junit4-rule:1.7.3"* testCompile "org.powermock:powermock-classloading-xstream:1.7.3"*/