草帽路飞UU 发表于 2022-10-27 15:33:27

单测是如何有有序井然的进行测试的

1.1 项目中引入单测框架



   单测依赖介绍如下:

  // JUnit4:本地单元测试

      'junit:junit:4.13.2',

      'androidx.test:core:1.4.0',

  // Robolectric:本地单元测试依赖 Android框架

      'org.robolectric:robolectric:4.4',

  // Mockito:本地单元测试模拟框架

      "org.mockito:mockito-core:3.12.4",

  // mock final类时出现错误:Mockito cannot mock/spy because : - final class,增加如下模拟框架

      'org.mockito:mockito-inline:3.12.4',

  // PowerMock:Mockito的一种扩展(以实现完成对private/static/final方法的Mock)

      'org.powermock:powermock-module-junit4:2.0.9',

      'org.powermock:powermock-api-mockito2:2.0.9'



   比如在 app Module 的 build.gradle 的 dependencies 下,依赖 JUnit4 如下:testImplementation 'junit:junit:4.13.2'

   UI 测试暂不做,但为了区分依赖,也罗列如下:

  // AndroidJUnitRunner and JUnit Rules:插桩单元测试

      'androidx.test:runner:1.4.0',

      'androidx.test:rules:1.4.0',

  // runner 和 rules 的扩展包:@RunWith(AndroidJUnit4.class) 在此扩展包的 runners 下

      'androidx.test.ext:junit:1.1.3',

  // Espresso:Android 界面测试

      'androidx.test.espresso:espresso-core:3.4.0'



   依赖 espresso 如下:androidTestImplementation ‘androidx.test.espresso:espresso-core:3.4.0'

   UI 测试时需在defaultConfig中添加:testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

  defaultConfig {

      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

  }


   UI 测试覆盖率统计开关打开:testCoverageEnabled true

  android {

      buildTypes {

        debug {

              testCoverageEnabled true

        }

        release {

              minifyEnabled false

              proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

        }

      }

  }



   单测可以访问编译版本的资源:includeAndroidResources = true

  android {

      testOptions {

        unitTests {

              includeAndroidResources = true

        }

      }

  }



  1.2 编写单测代码

  其实编写单测和写代码是一样的,只是使用不同工具完成功能或测试。具体操作的话,在 app/src/test/java/包名/ 下创建和代码一样的包结构,然后新建测试文件,编写单测代码即可。有如下注意事


项:

  ·测试文件命名:一般是文件名加上Test后缀,比如针对 TimeUtils.kt 这个文件测试,那么测试文件可命名为TimeUtilsTest。

  · 测试文件存放路径:细心的童鞋应该看到了,上一条的 TimeUtils.kt 这个文件是 kotlin 语言编写的,那么测试文件应该放在与 kotlin 相关的目录下,简单来说就是在包名和包结构中添加一层 kotlin 文


件夹,即:app/src/test/java/包名/kotlin/ 。为保持统一测试代码也建议用 kotlin 书写。

  · 测试方法命名:期望输出_测试场景,如 fiveMethodsShouldBeInvoked_WhenInitData

  3.4 单元测试代码分析

  为了给应用开发者一个直观的印象,这里还是决定贴出一份单测代码,如有不足之处,还请海涵:

  @Config(shadows = , sdk = , application = BaseTestApplication::class)

  class ProfilePresenterTest : BaseTestRobolectricClass() {


      @Spy

      lateinit var v: ProfileContract.V


      lateinit var p: ProfilePresenter


      @Before

      fun setUp() {

        MockitoAnnotations.openMocks(this)

        p = spy(ProfilePresenter::class.java)

        p.attachToView(v)

      }


      /**

     * 命名规则:期望输出_测试场景

     */

      @Test

      fun fiveMethodsShouldBeInvoked_WhenInitData() {

        p.initData()

        verify(v).updateWeight("")

        verify(v, never()).finishActivity()

        verify(v, atLeastOnce()).updateHeight("")

        verify(v, atLeast(1)).updateExerciseGoal("")

        verify(v, times(1)).updateExerciseFrequency("")

        verify(v, atMost(1)).updateExerciseTime("")

        // 检查是否所有的用例都涵盖了,如果没有将测试失败。放在所有的测试后面

        verifyNoMoreInteractions(v)

      }

      @Test

      fun finishActivityMethodShouldBeInvoked_WhenResetUserInfo() {

        PrivateAccessor.invoke<》rofilePresenter>(p, "resetUserInfo")

        verify(v).finishActivity()

      }


      companion object {

        private val TAG = ProfilePresenterTest::class.java.simpleName

      }


  }



  第一行的 @Config 部分可参考 Robolectric 框架。

  第二行继承了 BaseTestRobolectricClass 文件,它是作为单测代码的基类,稍后贴出源码。

  @Spy 与 Mockito.spy() 方法相同,只是一个使用注解方便些。

  fiveMethodsShouldBeInvoked_WhenInitData 为测试方法,verify 验证 initData 方法执行后,有5个方法会执行一次,never() 与 times() 等都是限定验证时方法的调用次数的。

  最后一个方法用到了 PrivateAccessor 类,它可以通过反射的方式支持验证私有方法和属性。

  BaseTestRobolectricClass 源码参考:

  @RunWith(RobolectricTestRunner::class)

  @Config(shadows = , sdk = , application = BaseTestApplication::class)

  abstract class BaseTestRobolectricClass {

      protected val mContext: Context = ApplicationProvider.getApplicationContext()

      companion object {

        @JvmStatic

        protected val TAG: String = this::class.java.simpleName


        @BeforeClass

        @JvmStatic

        fun setup() {

              ShadowLog.stream = System.out

        }

      }


  }



  2.1 统计覆盖率

  在 src/test/java 上右键选择如图 Run...,会跑整体单测代码。跑完后还会在写过单测代码的文件后显示单测覆盖率。也可导出覆盖率为 HTML 文件,但不比 AS 准确。





2.2 覆盖率统计 AS 中以及导出 HTML 文件的差异

 现象:AS中总代码行高于生成的HTML文件,所以显示的代码行覆盖率低于生成的HTML文件。

 原因:见截图。可知,HTML文件代码行中,并未包含activity、fragment和view相关的代码行。(不知道是因为没写UI测试导致的,或是AS导HTML时导致的)

 解决:目前开发时,以AS为准。



3 总结

 一般来说,单测初级阶段,在统计出覆盖率后,行覆盖率达到25%或更高指标时,就算差不多了。但写单测的路也不应就此停下,在维护代码过程中会涉及对单测的修改;在后面新增功能代码时也需新增单


测代码。






页: [1]
查看完整版本: 单测是如何有有序井然的进行测试的