Android unit test access guide

img

basic introduction

definition

Unit test is to verify the specified input START actual result matches the expected result of the test.

The purpose of access unit testing

Benefits of unit testing

  • Improve the stability of the code and ensure that the logic and boundaries of the code can be covered

  • Automated testing, conducive to self-test and refactoring testing

  • Promote code design, so that the code has clear input and output, and the functions of each level are clear

Problems with unit testing

  • Some situations will lead to longer total development time

  • Function update, unit test code must be updated synchronously

Key concept

  • Subject: usually a public method of a public class , that is, a process node in the business process;

  • Purpose: To verify whether the processing result of the process node matches the expected result;

  • Focus: specify the input, expected results, actual results, the test object should not be concerned about any internal processes , any internal details

  • Number of use cases: It is necessary to cover most of the combinations of input and output , not a simple correspondence between a test subject and a use case

Input set, expected result

  • Input set: All possible input set types of the test subject, including normal input and abnormal input. Most of the inputs in the input set are established when the unit test code of the process node is written for the first time, and a few (mostly abnormal inputs) are added to the input set with the occurrence of failures in subsequent iterations;

  • Expected results: a preset input specific scene after the next node is input to the process, the desired results. The expected result may be a state, a single behavior, or a chain of behaviors. In principle, the expected result will not be a private state or behavior ( private method calls).

  • Principle: The input corresponds to the expected result one-to-one, and if there is input, there must be result feedback; (this means that reflection requires code design to have clear layering and input and output)

Test classification

According to the types of expected results, unit tests can be divided into two categories:

  • State test: The expected result is mostly the return value and the member variables exposed by the class of the test subject.

  • Behavior testing: The expected results are mostly specific behaviors (chains), specifically calls to other public methods:

    • Internal Behavior: To prevent the inner nested test of public internal methods do unit testing other public method, also as expected results;
    • Abnormal behavior: abnormal input produces abnormal results, which may be throwing Exception
    • Behavior chain: When the specified input may produce multiple behaviors of a system that need attention, the verification of the test should verify this series of behaviors instead of the last behavior:

Unit test access process

|img

Dimensions of unit testing

Classified by Android directory

  • The androidTest directory should contain tests that are run on real or virtual devices. Such tests include integration tests, end-to-end tests, and other tests that cannot be verified by JVM alone.

  • The test directory should contain tests that are run on the local computer, such as unit tests.

Classified by operating environment

Virtual environment test

JUnit test

Running directly in the PC-side Java virtual machine environment, it can only test the standard java package content, not the Android context, and the test speed is faster.

Analog device

Robolectric and other tests, simulate most of the real machine environment on the PC side

Real machine test

The basic AndroidJUnit4 test code runs directly on the mobile device and has almost all contexts for Android code to run. When testing the use case, the original apk needs to be installed first, and the code of AndroidJUnit4 will be packaged into another apk, but the code runs in the process of the original apk, so it can completely simulate the state of the real machine. At the same time, it should be noted that when running a test case, the original apk runs through the apk startup process, and all the initialization behaviors of similar users will be automatically generated.

img

Classified by test content

img

The test pyramid (shown in the picture) illustrates how the application should include three types of tests (ie small, medium, and large tests):

  • Mini-tests are unit tests that verify the behavior of the application, one class at a time.

  • Medium testing refers to integration testing, which is used to verify the interaction between stack levels within a module or the interaction between related modules.

  • Large-scale testing refers to end-to-end testing, which is used to verify the user operation flow across multiple modules of the application.

Along the pyramid step by step, from small tests to large tests, the fidelity of various tests is gradually improved, but the execution time and workload required for maintenance and debugging work also increase. Therefore, you should write more unit tests than integration tests, and integration tests should be more than end-to-end tests. Although the proportion of various types of tests may vary depending on the use cases of the application, we usually recommend the following proportions of various types of tests: 70% for small tests, 20% for medium tests, and 10% for large tests.

Basic Practice of Unit Testing

Basic framework introduction

function points Support frame Key classes and methods
Frame/container JUnit @Before、@After、@Test、@RunWith()
Status verification JUnit Assert#assertXxxx()
Behavior verification-rely on Mock Mockito BDDMockito#given、BDDMockito#then
Behavior Verification-Static, Private Mock PowerMock PowerMockito
Four major component tests Roboletric
Real machine simulation AndroidJUnit4

BDD coding standard

Given (environment construction)

The Given step requires setting up an environment to provide a testing basis for subsequent operations. Its operations can be divided into several categories:

  1. Creation of the class where the test subject belongs;

  2. Injection of Mock dependent classes;

  3. Set the state of the test subject to the expected state (for example, when testing whether the wake-up method is correct, the voice needs to be put to sleep first)

  4. Prepare the data needed for testing;

  5. Instrumentation : When the test subject is affected by the return value and callback of some methods of the dependent class, these methods should be instrumented. Because the object after mock does not execute the real process, it usually fails to give valid results so that the test subject cannot execute normally.

When (execute test)

The When step directly executes the call of the test subject in the environment built by the Given step to trigger the required behavior or obtain the required state.

Then (Result Verification)

After the Then step gets the execution result of the When step, you need to verify whether the result meets expectations

Status verification

*/***
 ** 说明:行为测试Demo*
 ** 典型场景:业务逻辑类的测试多数为⾏行行为测试,类中的依赖普遍呈现错综复杂的情况,通常都需要将其中的依赖都Mock 出对应的类以完成⾏行行为测试;*
 ** 验证原则:预期结果多为 验证返回值、测试主体所在类暴露在外的成员变量量*
 ** 推荐流程:Given(环境搭建)  ->  When(执⾏行行测试)  ->  Then(结果验证)*
 *** ***@author\*** *wangshengxing*  *08.20* *2020*
 **/*
class StandardStateTest {
  val  TAG = **"StandardStateTest"**
  init {
    TesterLog.init()
  }
  */***
   ** 测试对象*
   ** 示例验证除法的正确性*
   **/*
  fun divide(a:Int,b:Int):Int{
    return a/b
  }
  */***
   ** Demo:测试除方法是否正常*
   **/*
  **@Test**
  fun canDivideWork(){
    //given
    val a=10
    val b=5
    val expect=2
    //when
    val result=divide(a,b)
    L.i(TAG, **"canDivideWork:** ${result}**"**)
    //then
    Assert.assertEquals(expect,result)
  }
}

Real machine status verification

*/***
 ** 说明:加解密工具验证*
 *** ***@author\*** *wangshengxing*  *08.19* *2020*
 **/*
**@RunWith**(AndroidJUnit4::class)
class CipherUnitTest {
  **@Test**
  fun encryptDecryptWork() {
    //given
    val str=**"123456"**
    //when
    val encryptData= CipherUtil.encryptData(str)
    val decryptData= CipherUtil.decryptData(encryptData)
    //then
    Assert.assertEquals(str, decryptData)
  }
}

Advanced unit testing

Spy and mock

Instrumentation refers to the custom modification of the original code behavior, usually in two forms, spy and mock. Both mock method and spy method can instrument objects. But the former takes over all the methods of the object, while the latter just mocks the calls with stubbing, and the other methods are still actual calls.

The standard of spy is: If piling is not performed, the real method is executed by default, and the pile implementation is returned if piling is performed.

*/***
 ** 对部分代码进行插桩*
 **/*
**@Test**
fun canSpyList(){
  //given
  val list: MutableList<String> = LinkedList<String>()
  val spy: MutableList<String> = spy(list)
  `when`(spy.size).thenReturn(100)
  //when
  spy.add(**"one"**)
  spy.add(**"two"**)
  L.i(TAG, **"canSpyList: list size** ${spy.size}**"**)
  //then
  Assert.assertEquals(spy[0], **"one"**)
  Assert.assertEquals(100, spy.size)
}

Behavior verification

*/***
 ** 说明:状态测试Demo*
 ** 典型场景:⼯工具类的测试偏向于状态测试。⼯工具类不不处理理具体业务,只提供算法、业务⽆无关的操作等,验证其返回结果即可完成测试;*
 ** 验证原则:*
 **    行为是否执行;*
 **    行为执行次数是否符合预期;*
 **    行为执行时的参数是否符合预期;*
 **    行为如果指定了监听器,监听器中操作是否符合预期;*
 *** ***@author\*** *wangshengxing*  *08.20* *2020*
 **/*
class StandardBehaviorTest {
  val  TAG = **"StandardBehaviorTest"**
  object NetUtil{
    fun hasConnected()=true
  }
  interface LoginListener{
    fun onLogin(name:String,password:String)
    fun onFailed()
  }
  class DemoModel{
    fun login(name:String,password:String,listener:LoginListener){
      if (NetUtil.hasConnected()) {
        //...
        listener.onLogin(name, password)
      }else{
        listener.onFailed()
      }
    }
  }
  init {
    TesterLog.init()
  }
  **@Test**
  fun canLoginWhenAllNormal(){
    //given
    val name=**"user"**
    val password=**"123456"**
    val listener = mock(LoginListener::class.*java*)
    val model=DemoModel()
    //when
    model.login(name, password, listener)
    //then 验证函数成功调用
    then(listener).should().onLogin(Mockito.anyString(),Mockito.anyString())
    //验证调用次数
    then(listener).should(Mockito.times(1)).onLogin(name,password)
    //Mockito.any<>() 返回值为null ,因此需要自己定义
    then(listener).should(Mockito.timeout(2000)).onLogin(UT.any(),UT.any())
    //验证onFailed没有调用过
    then(listener).should(Mockito.times(0)).onFailed()
    Mockito.verify(listener, Mockito.never()).onFailed()
  }
}

Guess you like

Origin blog.csdn.net/wsx1048/article/details/108428077