51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 669|回复: 0
打印 上一主题 下一主题

[原创] 单测是如何有有序井然的进行测试的

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 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 = [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<》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 = [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

x
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-4-20 03:23 , Processed in 0.067480 second(s), 24 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表