当一切准备就绪后,如何开始单测?

发表于:2022-10-25 09:48

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Swuagg    来源:CSDN

  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 = [ShadowLog::class, MockCA::class, MockDoExerciseApi::class, MockPortalApi::class], sdk = [23], 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<ProfilePresenter>(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 = [ShadowLog::class], sdk = [23], 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%或更高指标时,就算差不多了。但写单测的路也不应就此停下,在维护代码过程中会涉及对单测的修改;在后面新增功能代码时也需新增单测代码。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号