定义:单元测试就是针对最小的功能单元编写测试代码
Java程序最小的功能单元是方法,因此,对Java程序进行单元测试就是针对单个Java方法的测试。
JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计,使用最广泛。JUnit是事实上的单元测试的标准框架,任何Java开发者都应当学习并使用JUnit编写单元测试。
使用JUnit编写单元测试的好处在于:
对于高质量的代码来说,测试覆盖率应该在80%以上。
在编写单元测试的时候,我们要遵循一定的规范:
线上更多暴露的都是异常场景,所以在单元测试中有必要重点验证相关异常逻辑。
新建Android项目中app模块的build.gradle中会自动添加如下依赖:
testImplementation ‘junit:junit:4.12‘
androidTestImplementation ‘androidx.test.ext:junit:1.1.3‘
androidTestImplementation ‘androidx.test.espresso:espresso-core:3.4.0‘
在写单元测试的时候,有些对象在运行时是没有真实构造的,这个时候我们可以使用mock框架来模拟出一个可用的对象,需要添加如下依赖:
testImplementation ‘org.mockito:mockito-core:2.19.0‘
首先添加一个测试类,这里我添加一个简单的计算类:
public class Calculate {
private int mValue;
//+1
public int addOne() {
return ++mValue;
}
//-1
public int reduceOne() {
return --mValue;
}
}
然后在方法名上右键鼠标,如下图所示,点击"Test":
如果之前该类没有创建过Test类,则会提示你没有找到对应的测试类,点击“create Test”即会出现如下弹框:
点击ok按钮,会让你选择创建单元测试用例 还是 集成测试用例,如下图所示:
这里我们选择单元测试用例。 然后我们就会在test目录下找到对应的包名和测试文件了,如下图所示:
单元测试的时候用的最多的是上面3个注解:
@Before : 表示该方法在其他所有的Test方法执行之前都会执行一遍。一般用于初始化。
@After :表示每个Test方法执行结束后,都会执行一遍After方法。一般用于回收相关资源
@Test:标识该方法是一个测试方法
我们在刚才生成的CalculateTest类中增加如下代码:
public class CalculateTest {
private Calculate mCalculate;
@Before
public void setUp() throws Exception {
mCalculate = new Calculate();
}
@After
public void tearDown() throws Exception {
mCalculate = null;
}
@Test
public void addOne() {
Assert.assertTrue(mCalculate.addOne() == 1);
Assert.assertEquals(mCalculate.addOne(), 2);
}
@Test
public void reduceOne() {
Assert.assertTrue(mCalculate.reduceOne() == -1);
}
}
这里我们用到了Junit支持的断言来判断用例是否通过:
public class CalculateTest {
private Calculate mCalculate;
ExecutorService sSingleExecutorService = Executors.newSingleThreadExecutor();
......
@Test
public void addOneAsync() {
final CountDownLatch signal = new CountDownLatch(1) ;
sSingleExecutorService.execute(new Runnable() {
@Override
public void run() {
Assert.assertTrue(mCalculate.addOne() == 1);
Assert.assertEquals(mCalculate.addOne(), 2);
signal.countDown();
}
});
try {
signal.await();
} catch (InterruptedException e) {
e.printStackTrace() ;
}
}
}
如上代码所示,针对异步场景,我们可以使用到 CountDownLatch 类来针对性的暂停执行线程,直到任务执行完成后再唤醒用例线程。
注意,上面的try 才是暂停执行线程的核心。
有些时候我们不免会引用Android框架的对象,但是我们单元测试又不是运行在真实设备上的,在运行时是没有构建出真实的Android对象的,不过我们可以通过mock程序来模拟一个假的对象,并且强制让该对象的接口返回我们预期的结果。
1.添加mock依赖引用,前面添加依赖项的时候有提到:
testImplementation ‘org.mockito:mockito-core:2.19.0‘
2.导入静态会让代码简洁很多,这步不是必要的:
import static org.mockito.Mockito.*;
3.创建mock对象
TextView mockView = mock(TextView.class);
4.进行测试插桩
when(mockView.getText()).thenReturn("Junit Test");
下面我们看一个简单的例子。
首先我们在Calculate 类中新增一个简单的方法,获取TextView的文本信息:
public String getViewString(TextView view) {
return view.getText().toString();
}
然后我们在CalculateTest类中新增测试方法:
@Test
public void mockTest() {
TextView mockView = mock(TextView.class);
when(mockView.getText()).thenReturn("Junit Test");
assertEquals(mCalculate.getViewString(mockView), "Junit Test");
}
最后运行这个用例,正常通过。
当一个方法有参数时,我们可以批量验证不同参数值,对应的用例是否通过,而不用写多遍类似的代码
1.首先参数化测试,要求我们对测试类添加如下注解
@RunWith(Parameterized.class)
2.定义参数集合
- 方法必须定义为 public static 的
- 必须添加@Parameterized.Parameters
3.定义接收参数和期望参数对象
4.增加对应的用例
我们看下面的例子:
首先我们在Calculate 中添加一个有参数的add方法:
public class Calculate {
private int mValue;
......
public int add(int other) {
mValue += other;
return mValue;
}
}
接着修改测试类
@RunWith(Parameterized.class) //---------@1
public class CalculateTest {
private Calculate mCalculate;
private Integer mInputNumber; //---------@3
private Integer mExpectedNumber;
//---------@4
public CalculateTest(Integer input , Integer output) {
mInputNumber = input;
mExpectedNumber = output;
}
@Parameterized.Parameters //---------@2
public static Collection paramsCollection() {
return Arrays.asList(new Object[][] {
{ 2, 2 },
{ 6, 6 },
{ 19, 19 },
{ 22, 22 },
{ 23, 23 }
});
}
@Before
public void setUp() throws Exception {
mCalculate = new Calculate();
}
@After
public void tearDown() throws Exception {
mCalculate = null;
}
//---------@5
@Test
public void paramsTest() {
assertEquals(mExpectedNumber, Integer.valueOf(mCalculate.add(mInputNumber)));
}
}
@1 : 给类添加注解RunWith(Parameterized.class)
@2 : 添加数据集合方法,用@Parameterized.Parameters 注解修饰
@3 : 添加输入参数和期望参数
@4 : 添加构造方法,供给输入参数和期望参数赋值
@5 : 添加测试方法,直接使用输入参数和期望参数进行验证
异常验证通过@Test注解参数来指定:
@Test(expected = InvalidParameterException.class)
看下面具体的例子:
public class Calculate {
private int mValue;
public int addException(int other) {
if (other < 0) {
throw new InvalidParameterException();
}
return add(other);
}
}
测试类如下:
@RunWith(Parameterized.class)
public class CalculateTest {
private Calculate mCalculate;
@Test(expected = InvalidParameterException.class)
public void exceptionTest() {
mCalculate.addException(-1);
}
}
这里可以注意以下几点:
点击左侧绿色箭头,会弹出如上图菜单,单机Run 即可执行该用例。
如上图所示,选中测试类文件,右键执行 "Run 类名",就会批量执行该类所有的用例了
如上图所示,右键包名,执行"Run Test in 包名" 即可执行该包下所有类对应的用例
在执行完测试用例之后,我们可以导出测试报告,如下图所示:
如上图所示:点击converage按钮,在右边窗口会弹出如下覆盖情况,这里从3个方面统计测试覆盖度:
最后,我们可以导出覆盖报告.
本文由博客一文多发平台 OpenWrite 发布!
原文:https://www.cnblogs.com/pplovelyliu/p/15172697.html