项目中为了测试某一个代码单元而写的测试代码;用于执行项目中的目标函数并验证其逻辑状态或者结果。这些代码是白盒测试,能够检测目标代码的准确性和可靠性,在打包时单元测试的代码并不会被编译进入release apk中。
单元测试不能百分之百保证在集成联调时能够完全正确运行,但可以保证每一代码单元测试通过。
使用单元测试可以边重构边写单元测试,保证重构没有破坏原有的代码逻辑的准确性。
这个过程中,可以发现逻辑设计上的问题,代码编写方面的问题,比如一些边界条件处理、空对象的保护和类构造时参数的依赖问题,还有一些对Android Context的依赖问题。
如果没有单元测试的话,我们coding时为了调试某个功能,看界面是否显示正确,把APP运行起来,如果有错的话,改一点东西,再运行起来。。。有了单元测试,开发过程中可以更少的把APP运行起来,一两分钟的等待就可以验证代码逻辑是否正确,速度相对来说快很多。此外,通过单元测试能帮我们减少bug,从而减少了调试bug和fix bug的时间
如果没有单元测试,我们就需要在最后的集成联调中才能发现自己模块的问题,一旦问题比较严重的话,可能会让自己感觉有压力和恐慌。但是通过单元测试,可以有多一层的保证,可以更早的发现问题和解决问题,在集成时让自己更加闲庭信步。
src/test/java
目录中,@Test
public void testadd(int num1, int num2) {
MainActivity mainActivity = new MainActivity(); //before;创建待测类的对象
int sum = mainActivity.add(1,2); //on:调用待测方法
Assert.assertEquals(3,sum); //after:通过断言检测结果
}
运行:选中方法,右键 run 即可;
主要测试对象
问题:对于项目中依赖资源复杂情况,处理困难;对于对象、集合、Map等数据结构的比较(断言的可读性不友好),可使用Hamcest框架进行比较;
测试对象及环境:针对 复杂依赖情况;针对 非私有、非静态、非全局的方法、类的测试;
在测试环境中,通过Mockito mock出其他的依赖对象,用来替换真实的对象,使得待测的目标方法被隔离起来,
避免外界因素的影响和依赖
,能在我们预设的环境中执行,以达到两个目的:
问题:生成mock对象的原理有其局限性,如final类型、private类型以及静态类型的方法不能mock;
UI
测试,查询一个控件,模拟点击,验证逻辑;Activity
里面写的,一些业务逻辑都是使用私有方法,那么相对私有方法做验证,通过查询控件和通过UI
的展示来验证逻辑的正确与否,十分的复杂。需要使用Powermock
。Powermock
和Robolectric
存在着兼容问题,如果出现问题无法解决,可尝试放弃Robolectric
Google开源的测试框架,基于Instrumentation,更偏向于UI方面的自测化测试。
Espresso功能强大、执行速度很快的,但是它有一个重要的局限:只可以在被测App的Context中操作。意味着下列App自动化测试需求无法实现:应用的推送消息、同步联系人、从另一个应用程序进入被测App。
UIAutomator 2.0 发布后可以实现上述功能;采用脚本语言编写,集成失败…UIAutomator集成测试失败;
由Android提供的Instrumentation系统如,将单元测试代码运行在模拟器或者是真机上。虽然这种方式可以work,但是速度非常慢。
JUnit4 | Mockito | PowerMockito | Robolectric | UIAuutomator / Espresso | robotium | ActivityUnitTestCase / ActivityInstrumentationTestCase2 | |
---|---|---|---|---|---|---|---|
项目中依赖复杂的情况 | ✅ | ✅ | ✅ | ||||
依赖Android SDK 的情况 | ✅ | ||||||
UI测试 | ✅ | ✅ | ✅ | ||||
测试私有、全局、静态 | ✅ |
JUnit4 + Mockito + PowerMockito + Robolectric
使用:JUnit4 + PowerMockito
在Android studio project中,源代码默认放在src/main/java
下面的,
对应的Java相关的单元测试代码则是放在src/test/java
目录中;
src/androidTest/java
中存放的是Android相关的单元测试代码
依赖:默认是有的:testImplementation “junit:junit:$junit_version”
以 tosee-sdk 为例;src/main/java/tech/tosee/combine/models/TSPeer.kt
GOTO
选择Test
src/test/java
目录中;Android相关的单元测试代码在src/androidTest/java
中@Test
fun testEquals() {
//before 实例化 待测试的目标类,以及设计一些前提条件:
val tsPeer: TSPeer = TSPeer("userId", "gmsUserId", "nickname", "merchantId", "rtcUserId")
//on 执行操作,调用待测试的那个方法,同时获得结果:
val equals = tsPeer.equals(TSPeer("userid", "gmsUserId", "nickName", "merchantid", "rtcUserid"))
print("================================" + equals)//这个不被打
Log.i(TAG, "testEquals: ")
//after验证结果,通过断言来验证结果是否与预期一样:
assertTrue(equals)
}
注解:
@BeforeClass:针对所有测试,只执行一次,且必须为static void
@Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)
@Test:测试方法,在这里可以测试期望异常和超时时间
@Test(expected=ArithmeticException.class)检查被测方法是否抛出ArithmeticException异常
@Ignore:忽略的测试方法
@After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)
@AfterClass:针对所有测试,只执行一次,且必须为static void
testImplementation "org.mockito.kotlin:mockito-kotlin:3.2.0"
// PowerMock brings in the mockito dependency
testImplementation "org.powermock:powermock-module-junit4:2.0.4"
testImplementation "org.powermock:powermock-module-junit4-rule:2.0.4"
testImplementation "org.powermock:powermock-api-mockito2:2.0.4"
testImplementation "org.powermock:powermock-classloading-xstream:2.0.4"
如遇问题推荐阅读:Android 单元测试之PowerMock
@RunWith(PowerMockRunner.class)
@PrepareForTest({YourClassWithEgStaticMethod.class})
@PowerMockIgnore("javax.management.*")
若测试用例没有用@PrepareForTest
,那么不用加@RunWith(PowerMockRunner.class)
,反之亦然。
当你需要使用PowerMock进行Mock静态、final、私有方法等时候,就需要加@PrepareForTest
。要么使用这种注解的方式:
@RunWith(PowerMockRunner.class)
@PrepareForTest({YourClassWithEgStaticMethod.class})
要么使用注解加代码的方式
@PrepareForTest({YourClassWithEgStaticMethod.class})
MockitoAnnotations.initMocks(this);
注:@PrepareForTest({Example1.class, Example2.class, ...}) //声明多个静态类
class CommonExampleKotlin {
fun callArgumentInstance(file: File): Boolean {
return file.exists()
}
}
class CommonExampleKotlinTest {
@Test
fun testCallArgumentInstance() {
//1。创建一个mock对象
val file = PowerMockito.mock(File::class.java)
val commonExample = CommonExample()
//2。指定mock对象具体的行为
PowerMockito.`when`(file.exists()).thenReturn(true)
//3。将mock对象作为参数传递给测试方法,执行测试方法。
assertTrue(commonExample.callArgumentInstance(file))
}
}
当使用 PowerMockito.whenNew 方法时,必须加注解 @PrepareForTest 和 @RunWith 。@PrepareForTest 里写的类是需要 mock 的 new 对象代码所在的类。
class CommonExampleKotlin {
/**
* 2) Mock方法内部new出来的对象
*/
fun callArgumentInstance(path: String?): Boolean {
val file = File(path)
return file.exists()
}
}
@Test
//@RunWith(PowerMockRunner::class.java)
@PrepareForTest(CommonExampleKotlin::class)
//2) Mock方法内部new出来的对象
fun testCallArgumentInstance2() {
//代码实现RunWith
MockitoAnnotations.initMocks(this);
//1。创建一个mock对象
val file = PowerMockito.mock(File::class.java)
val commonExampleKotlin = CommonExampleKotlin()
//2。指定当以参数为"fileName"创建File对象的时候,返回已经mock的File对象。
PowerMockito.whenNew(File::class.java).withArguments("fileName").thenReturn(file)
//3。指定mock对象具体的行为
PowerMockito.`when`(file.exists()).thenReturn(true)
//实际上此处该方法 并未成功创建文件,
assertTrue(!commonExampleKotlin.callArgumentInstance("fileName"))
val newFile = Mockito.mock(File::class.java)
newFile.exists()
Mockito.verify(newFile).exists()
}
public class EmployeeService {
public int getTotalEmployee() {
EmployeeDao employeeDao = new EmployeeDao();
return employeeDao.getTotal();
}
}
getTotalEmployee()因为测试的时候是不能修改这个方法的,不管里面是什么东西,局部变量我们也是无法触摸的,所以!智能模拟,不能更改,这个改正很可能造成系统的缺陷。不注意就完了。非常的需要小心,作为测试人员的话!
/**
* 采用 PowerMock 进行测试
*/
@Test
public void testGetTotalEmployeeWithMock() {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
try {
//没得参数的够造!返回模拟的局部变量!
PowerMockito.whenNew(EmployeeDao.class).withNoArguments()
.thenReturn(employeeDao);
PowerMockito.when(employeeDao.getTotal()).thenReturn(10);
EmployeeService service = new EmployeeService();
int total = service.getTotalEmployee();
assertEquals(10, total);
} catch (Exception e) {
fail("测试失败.");
}
}
}
class CommonDependency {
/**
* 3) Mock普通对象的final方法
*/
final fun isAlive(): Boolean {
// do something
return false
}
}
class CommonExampleKotlin {
/**
* 3) Mock普通对象的final方法
*/
open fun callFinalMethod(example: CommonDependency): Boolean {
return example.isAlive()
}
}
@Test
//final方法所在的类。
@PrepareForTest(CommonDependency::class)
fun testCallFinalMethod() {
val mock: CommonDependency = PowerMockito.mock(CommonDependency::class.java)
val commonExampleKotlin = CommonExampleKotlin()
PowerMockito.`when`(mock.isAlive()).thenReturn(true)
assertTrue(commonExampleKotlin.callFinalMethod(mock))
}
添加依赖:
//Mockito
testImplementation "org.mockito:mockito-inline:3.3.3"
androidTestImplementation 'org.mockito:mockito-android:3.3.3'
class CommonExampleKotlin {
/**
* 4) Mock普通类的静态方法
*/
fun printUUID(): String {
return Utils.generateNewUUId()
}
}
object MUtils {
@JvmStatic
open fun generateNewUUId(): String {
return UUID.randomUUID().toString()
}
}
//语句告诉JUnit用PowerMockRunner来执行测试。
@RunWith(PowerMockRunner::class)
//语句告诉PowerMock准备Employee类进行测试。适用于模拟final类或有final, private, static, native方法的类。
@PrepareForTest(Utils::class)
class CommonExampleKotlinTest {
//4) ??????? Mock普通类的静态方法
val res = "Return UUID"
@Test
fun testPrintUUID() {
val commonExampleKotlin = CommonExampleKotlin()
PowerMockito.mockStatic(MUtils::class.java)
PowerMockito.`when`(MUtils.generateNewUUId()).thenReturn(res)
print(commonExampleKotlin.printUUID())
assert(res.equals(commonExampleKotlin.printUUID()))
}
}
class CommonExampleKotlin {
/**
* 5) Mock 私有方法
*/
fun callPrivateMethod(): Boolean {
return isExist()
}
private fun isExist(): Boolean {
return false
}
}
//语句告诉JUnit用PowerMockRunner来执行测试。
@RunWith(PowerMockRunner::class)
//语句告诉PowerMock准备Employee类进行测试。适用于模拟final类或有final, private, static, native方法的类。
@PrepareForTest(CommonExampleKotlin::class)
class CommonExampleKotlinTest {
//5)mock 私有方法
@Test
fun testCallPrivateMethod() {
val mock:CommonExampleKotlin = PowerMockito.mock(CommonExampleKotlin::class.java)
PowerMockito.`when`(mock.callPrivateMethod()).thenCallRealMethod()
PowerMockito.`when`(mock, "isExist").thenReturn(true)
print(mock.callPrivateMethod())
assertTrue(mock.callPrivateMethod())
}
}
class CommonExampleKotlin {
/**
* 6) Mock系统类的静态和final方法
*/
fun callSystemStaticMethod(str: String?): String {
return System.getProperty(str)
}
}
//语句告诉JUnit用PowerMockRunner来执行测试。
@RunWith(PowerMockRunner::class)
//语句告诉PowerMock准备Employee类进行测试。适用于模拟final类或有final, private, static, native方法的类。
//@PrepareForTest(Utils::class)
@PrepareForTest(CommonExampleKotlin::class)
class CommonExampleKotlinTest {
//6) Mock系统类的静态和final方法
@Test
fun testcallSystemStaticMethod() {
val commonExample = CommonExampleKotlin();
PowerMockito.mockStatic(System::class.java);
PowerMockito.`when`(System.getProperty("aaa")).thenReturn("bbb");
assertEquals("bbb", commonExample.callSystemStaticMethod("aaa"));
}
}
class CommonExampleKotlin {
/**
* 7) Mock普通类的私有变量
*/
private val STATE_NOT_READY: Int = 0
private val STATE_READY = 1
private val mState = STATE_NOT_READY
fun doSomethingIfStateReady(): Boolean {
return if (mState == STATE_READY) {
// DO some thing
true
} else {
false
}
}
}
class CommonExampleKotlinTest {
//7) Mock普通类的私有变量
@Test
fun testdoSomethingIfStateReady() {
val sample = CommonExampleKotlin()
Whitebox.setInternalState(sample, "mState", 1)
assertTrue(sample.doSomethingIfStateReady())
}
}
注:当需要mock私有变量mState的时候,不需要加注解@PrepareForTest和@RunWith,而是使用Whitebox来mock私有变量mState并注入你预设的变量值。
程序中源代码被测试的比例和程度,所得比例称为代码覆盖率。
if
和switch
语句计算分支覆盖率。统计在方法中分支执行数量、未执行数量的信息。但要注意,异常处理不在此计算范围内。Powermock
有兼容性问题。附:
如有侵权,请联系删帖。
推荐阅读:
Mockito&PowerMockito的原理与使用
Java单元测试技巧之PowerMock
史上最轻量!阿里新型单元测试Mock工具开源了
注意