基本概念
维基百科的定义:单元测试(又称为模块测试, Unit Testing)是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
《软件测试方法和技术》(朱少民 清华大学出版社 2005年7月第一版):
单元测试是对软件基本组成单元的测试。单元测试的对象是软件设计的最小单位——模块。很多参考书中将单元测试的概念误导为一个具体函数或一个类的方法。一个最小的单元应该有明确的功能、性能定义、接口定义而且可以清晰地与其他单元区分开来。一个菜单、一个显示界面或者能够独立完成的具体功能都可以是一个单元。某种意义上单元的概念已经扩展为组件(component)
名词解释
- JUnit: java单元测试最普及的框架
- testCompile: 用于
src/test
下的代码测试,在JVM环境下 - androidTestCompile: 用于
src/androidTest
下的代码测试,在Android device(or an emulator)
小特点说明
- 在JUnit 4中可以不用在测试方法前加
test
前缀了(JUnit 3中还需要加)
AS依赖
应用模块的 build.gradle 文件中指定测试库依赖项(来自Google官方文档):
dependencies {
// Required for local unit tests (JUnit 4 framework)
testCompile 'junit:junit:4.12'
// Required for instrumented tests
androidTestCompile 'com.android.support:support-annotations:24.0.0'
androidTestCompile 'com.android.support.test:runner:0.5'
}
Android项目中,你创建单元测试文件在module-name/src/test/java/
路径下。这个路径在创建项目的时候已经创建了。
你也需要设置项目依赖,如上例所示 JUnit4框架,如果你的单元测试需要用到Android的依赖,则需要Mockito库,下文有关于Mock的介绍。
创建AS单元测试
注解解释
注解可以方便的设置你想要测试的功能
- @BeforeClass:针对类中所有测试,只执行一次,且必须为static void
- @Before:初始化方法,针对每个测试方法执行一次
- @Test:测试方法,包括使用断言判定待测试方法的输出是否正确、是否抛出特定异常等
- @After:释放资源,针对每个测试方法执行一次
- @AfterClass:针对所有测试,只执行一次,且必须为static void
- @Ignore:忽略的测试方法
- @RunWith(Suite.class) @Suite.SuiteClasses({ *.class}) 指定测试套件,一次运行多个测试类中的测试方法
断言
除了使用assertEquals、assertTrue,为了提高可读性,还可以使用JUnit4.4引入的Hamcrest框架,assertThat。详细的断言可以看API文档。
举个例子
用网上最流行的例子,Calculator.java
public class Calculator{
public double sum(double a, double b){
return0;
}
public double substract(double a, double b){
return0;
}
public double divide(double a, double b){
return0;
}
public double multiply(double a, double b){
return0;
}
}
相应的单元测试类
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest{
private Calculator mCalculator;
@Before
public void setUp() throws Exception {
mCalculator = new Calculator();
}
@Test
public void testSum() throws Exception {
//expected: 6, sum of 1 and 5
assertEquals(6, mCalculator.sum(1, 5));
}
@Test
public void testSubstract() throws Exception {
assertEquals(1, mCalculator.substract(5,4));
}
@Test
public void testDivide() throws Exception {
assertEquals(4d, mCalculator.divide(20d, 5d));
}
@Test public void testMultiply() throws Exception {
assertEquals(10d, mCalculator.multiply(2d, 5d));
}
}
Mock
有时在单元测试中需要用到Android相关的类,比如说Context、Activity,但是在其他JUnit中不存在这些类,这时我们就要用到Mock和Mockito,是一个模拟测试框架,可以获取你需要的各种类。
- 如何依赖
repositories { jcenter() }
dependencies {
testCompile "org.mockito:mockito-core:2.+"
}
Mockito例子
更多信息见官网API文档
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import android.content.SharedPreferences;
@RunWith(MockitoJUnitRunner.class)
public class UnitTestSample {
private static final String FAKE_STRING = "HELLO WORLD";
@Mock
Context mMockContext;
@Test
public void readStringFromContext_LocalizedString() {
// Given a mocked Context injected into the object under test...
when(mMockContext.getString(R.string.hello_word))
.thenReturn(FAKE_STRING);
ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);
// ...when the string is returned from the object under test...
String result = myObjectUnderTest.getHelloWorldString();
// ...then the result should be the expected one.
assertThat(result, is(FAKE_STRING));
}
}