51Testing软件测试论坛

标题: React Native 代码覆盖率获取探索 (一) [打印本页]

作者: 巴黎的灯光下    时间: 2017-6-29 16:41
标题: React Native 代码覆盖率获取探索 (一)
背景最近开始做覆盖率的落地,刚完成 android jacoco 的,结果就遇上了项目架构调整,开始往 react native 方向走。而且按照新架构,主要逻辑代码都是在 react native 上编写,所以探究一下怎么获取 react native 的 js 代码覆盖率。
搭建开发环境不得不说,react native 的开发环境搭建文档写得非常好,国内也有一个 React Native 中文网 ,把这部分文档完全翻译过来,并且接地气地写上了国内的一些镜像地址。
搭建开发环境
具体大家可以直接看上面链接里的流程,这里就不再详述了。最终完成了 iOS 和 android 开发环境的搭建,并用 react-native run-ios 和 react-native run-android 两个命令验证成功。
下载并初始化示例项目默认的 AwesomeProject 基本没有逻辑,做覆盖率尝试不是很够。找了下,找到了 facebook 的 2016年 f8 app 。直接使用它就好了。
下载及初始化方法(简要版,详细的建议参照 github 上的文档):
  1. git clone https://github.com/fbsamples/f8app.git
  2. cd f8app && npm install

  3. # ios dependencies
  4. cd ios; pod install; cd ..

  5. # Import sample data(官方的说明,实际上运行百分百失败。详细原因请看末尾的踩坑记录。这里后续命令使用能成功运行的命令)
  6. # npm run import-data # 这条命令运行会报错,请使用下面的命令
  7. # download db
  8. wget https://raw.githubusercontent.com/ReactWindows/f8app/data/mongodb/db.zip

  9. # unzip db
  10. unzip db.zip -d f8_db

  11. # run mongodb base on db backup above
  12. mongod --storageEngine wiredTiger --dbpath f8_db/db

  13. # Make sure mongodb is running. If it's running, one line starts with "mongodb" should exist
  14. lsof -iTCP:27017 -sTCP:LISTEN
  15. # If it's running, it should looks like below:
  16. # COMMAND   PID        USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
  17. # mongod  99400 hengjiechen    8u  IPv4 0x5fba934ae787aa99      0t0  TCP *:27017 (LISTEN)

  18. # Start Parse/GraphQL servers
  19. npm start
复制代码
通过查看 http://localhost:8080/dashboard 和 http://localhost:8080/graphql 确保服务开启成功,并且数据库有相应数据。

(主要看后面蓝色底色的那张图,确认有数据)
  1. # Android
  2. react-native run-android
  3. adb reverse tcp:8081 tcp:8081   # required to ensure the Android app can
  4. adb reverse tcp:8080 tcp:8080   # access the Packager and GraphQL server

  5. # iOS
  6. react-native run-ios<code></code>
复制代码
运行效果:

收集单测覆盖率根据 Unit Testing 可以看到,收集覆盖率的组件为 istanbul ,查了下,基本 js 覆盖率收集用的都是这个工具。
另外,由于 f8 项目是官方写得,Facebook 不使用上面 Unit Testing 里面使用的 Mocha 框架,而是用 Facebook 自己的 jest。经过查阅,jest 本身带有覆盖率相关的配置项,集成了使用 istanbul 收集覆盖率的功能,而且 f8 里面也有一些 jest 的测试用例,直接拿来尝鲜下。
单测相关文件在 ./js/reducers/__tests__ 文件夹里面有一些,通过 npm test 命令即可自动执行,结果类似下面:
  1. $ npm test

  2. > F8v2@0.0.1 test /Users/hengjiechen/Develop/ReactNative/f8app
  3. > jest

  4. Using Jest CLI v13.0.0, jasmine2, babel-jest
  5. PASS  js/reducers/__tests__/maps-test.js (0.358s)
  6. PASS  js/reducers/__tests__/schedule-test.js (0.376s)
  7. PASS  js/reducers/__tests__/notifications-test.js (0.381s)
  8. PASS  js/tabs/schedule/__tests__/formatDuration-test.js (0.108s)
  9. PASS  js/tabs/schedule/__tests__/formatTime-test.js (0.099s)
  10. 12 tests passed (12 total in 5 test suites, run time 4.813s)
复制代码
然后根据官方的 配置文档,加入覆盖率相关配置。需要修改 ./package.json 文件,具体修改如下:
  1. diff --git a/package.json b/package.json
  2. index 17d9914..27d060e 100644
  3. --- a/package.json
  4. +++ b/package.json
  5. @@ -56,7 +56,9 @@
  6.        "providesModuleNodeModules": [
  7.          "react-native"
  8.        ]
  9. -    }
  10. +    },
  11. +    "collectCoverage": true,
  12. +    "coverageDirectory": "coverage"
  13.    },
  14.    "engines": {
  15.      "node": ">=5.0",
复制代码
PS:也可以把上面的代码块内容保存到 coverage.patch 文件,存到 f8app 根目录,然后 git apply coverage.patch 直接应用变更。
加入覆盖率配置后,同样 npm test ,输出如下:
  1. $ npm test
  2. > F8v2@0.0.1 test /Users/hengjiechen/Develop/ReactNative/f8app
  3. > jest

  4. Using Jest CLI v13.0.0, jasmine2, babel-jest
  5. PASS  js/reducers/__tests__/schedule-test.js (0.166s)
  6. PASS  js/reducers/__tests__/maps-test.js (0.166s)
  7. PASS  js/reducers/__tests__/notifications-test.js (0.21s)
  8. PASS  js/tabs/schedule/__tests__/formatTime-test.js (0.079s)
  9. PASS  js/tabs/schedule/__tests__/formatDuration-test.js (0.087s)
  10. 12 tests passed (12 total in 5 test suites, run time 3.347s)
  11. ------------------------|----------|----------|----------|----------|----------------|
  12. File                    |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
  13. ------------------------|----------|----------|----------|----------|----------------|
  14. reducers/              |    70.37 |    71.43 |    73.33 |    81.82 |                |
  15.   createParseReducer.js |      100 |      100 |      100 |      100 |                |
  16.   maps.js               |      100 |      100 |      100 |      100 |                |
  17.   notifications.js      |    59.09 |       56 |     62.5 |    65.52 |... 107,108,110 |
  18.   schedule.js           |    77.78 |    78.57 |       75 |      100 |                |
  19. tabs/schedule/         |      100 |    85.71 |      100 |      100 |                |
  20.   formatDuration.js     |      100 |      100 |      100 |      100 |                |
  21.   formatTime.js         |      100 |    66.67 |      100 |      100 |                |
  22. ------------------------|----------|----------|----------|----------|----------------|
  23. All files               |    78.38 |     74.6 |    77.78 |    88.24 |                |
  24. ------------------------|----------|----------|----------|----------|----------------|
复制代码
html 的覆盖率报告放在 ./coverage/lcov-report/index.html 中。同时也有 json 格式、xml 格式的覆盖率数据。
小结这次探索基本了解了 rn 项目的大概结构,以及单测中  js 覆盖率怎么获取。基本确定了采用类似 middleware 嵌入到应用中的方式应该可以用来做手工覆盖率收集。具体操作方案后续再继续探索。
意外惊喜在了解 istanbul 的时候,找到了一个好玩的项目:istanbul-middleware 。在项目根目录执行一些命令,即可得到可以实时显示覆盖率的小 Demo 。
使用方法如下命令:
  1. git clone https://github.com/gotwarlost/istanbul-middleware.git<code></code>
复制代码
# 启动带有实时覆盖率报告的网站
  1. cd istanbul-middleware/test/app
  2. npm install && node index.js --coverage<code></code>
复制代码
打开 http://localhost:8888 可以访问这个小网站,打开 http://localhost:8888/coverage/ 可以访问实时更新的覆盖率报告(实时更新是指操作后 F5 更新覆盖率报告即可得到最新的覆盖率情况,也可以自己二次开发加个轮询实现真正的实时显示)
大家可以探索下,看通过哪些用例可以让行覆盖率和分支覆盖率达到100% : ) 。后面甚至可以在这个 Demo 基础上进行小网站的功能扩展,甚至拿个单页应用来替代这个网站,开展一下覆盖率小竞赛,让大家更有乐趣地去了解和应用代码覆盖率这个工具。
踩坑记录运行 npm run import-data 提示错误错误信息:
  1. > F8v2@0.0.1 import-data /Users/hengjiechen/Develop/ReactNative/f8app
  2. > babel-node ./scripts/import-data-from-parse.js

  3. Loading Speakers
  4. SyntaxError: Unexpected token P in JSON at position 0
  5.     at Object.parse (native)
  6.     at /Users/hengjiechen/Develop/ReactNative/f8app/node_modules/node-fetch/lib/body.js:43:15
  7.     at process._tickDomainCallback (internal/process/next_tick.js:129:7)
复制代码
原因: 导入数据依赖的一个远程服务器 Parse.com 。这个服务器已经关站了,导致导入数据时服务端会返回 Parse.com has shutdown - https://parseplatform.github.io/,json 解析器解析出错。
解决方法:改用 github 上其它同学备份的 mongodb 数据库。
  1. # download db
  2. wget https://raw.githubusercontent.com/ReactWindows/f8app/data/mongodb/db.zip

  3. # unzip db
  4. unzip db.zip -d f8_db

  5. # run mongodb base on db backup above
  6. mongod --dbpath f8_db
复制代码
参考资料:https://github.com/fbsamples/f8app/issues/149, https://github.com/fbsamples/f8app/issues/156



作者: 巴黎的灯光下    时间: 2017-6-29 16:43
使用 react-native run-ios 启动 iOS 客户端时,编译失败

错误信息:

  
  1. An application bundle was not found at the provided path.
  2.     Provide a valid path to the desired application bundle.
  3.     Print: Entry, ":CFBundleIdentifier", Does Not Exist
复制代码

原因:实际上出错的不是这个地方,是代码里有一个地方本身就会编译出错。react-native cli 的错误信息不大正确。

解决方法:参考下面的内容修改 f8app/node_modules/react_native/React/Views/RCTScrollView.m

  1.     ...
  2.     @implementation RCTCustomScrollView
  3.     {
  4.     __weak UIView *_dockedHeaderView;
  5.     }

  6.     // 增加下面这一行
  7.     @synthesize refreshControl = _refreshControl;

  8.     - (instancetype)initWithFrame:(CGRect)frame
  9.     {
  10.     if ((self = [super initWithFrame:frame])) {
  11.     [self.panGestureRecognizer addTarget:self action:@selector(handleCustomPan:)];
  12.     }
  13.     return self;
  14.     }
  15.     ...
复制代码

参考资料:https://github.com/fbsamples/f8app/issues/137
使用启动 ios 客户端后,terminal 报错

错误信息:

Failed to build DependencyGraph: Watchman error: Cannot read property 'root' of null. Make sure watchman is running for this project. See https://facebook.github.io/watchman/docs/troubleshooting.html.

原因:watchman 有问题,重装即可解决。

解决方法:brew uninstall watchman && brew install watchman -HEAD

参考资料:https://github.com/facebook/react-native/issues/1875
使用 react-native run-android 启动时报错

错误信息:

    Execution failed for task ':app:processDebugManifest'. > Manifest merger failed : Attribute activity#com.facebook.FacebookActivity@theme value=(@android:style/Theme.Translucent.NoTitleBar) from AndroidManifest.xml:55:11-70

错误原因:android mainfest 文件内容有问题,需要修改

解决方法:删掉 android/app/src/main/AndroidManifest.xml 文件里面的这一行(应该是第55行):

android:theme="@android:style/Theme.Translucent.NoTitleBar"

参考资料:https://github.com/fbsamples/f8app/issues/134
运行 android 客户端时,关闭首次打开的登录界面后直接 crash

  1. ogcat 错误日志:

  2.     E/AndroidRuntime( 1958): FATAL EXCEPTION: IntentService[RNPushNotification]
  3.     E/AndroidRuntime( 1958): Process: com.facebook.f8, PID: 1958
  4.     E/AndroidRuntime( 1958): java.lang.IllegalAccessError: Method 'void android.support.v4.content.ContextCompat.<init>()' is inaccessible to class 'com.google.android.gms.iid.zzd' (declaration of 'com.google.android.gms.iid.zzd' appears in /data/app/com.facebook.f8-1/base.apk)
  5.     E/AndroidRuntime( 1958): at com.google.android.gms.iid.zzd.zzdL(Unknown Source)
  6.     E/AndroidRuntime( 1958): at com.google.android.gms.iid.zzd.<init>(Unknown Source)
  7.     E/AndroidRuntime( 1958): at com.google.android.gms.iid.zzd.<init>(Unknown Source)
  8.     E/AndroidRuntime( 1958): at com.google.android.gms.iid.InstanceID.zza(Unknown Source)
  9.     E/AndroidRuntime( 1958): at com.google.android.gms.iid.InstanceID.getInstance(Unknown Source)
  10.     E/AndroidRuntime( 1958): at com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService.onHandleIntent(RNPushNotificationRegistrationService.java:20)
  11.     E/AndroidRuntime( 1958): at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
  12.     E/AndroidRuntime( 1958): at android.os.Handler.dispatchMessage(Handler.java:102)
  13.     E/AndroidRuntime( 1958): at android.os.Looper.loop(Looper.java:135)
  14.     E/AndroidRuntime( 1958): at android.os.HandlerThread.run(HandlerThread.java:61)
  15.     W/ActivityManager( 745): Force finishing activity com.facebook.f8/.MainActivity

  16.     错误原因:PushNotificationController 部分有问题,需要注释掉相关功能。

  17.     解决方案:参考以下 diff 内容修改 js 文件夹里的两个文件。

  18.     diff --git a/js/F8App.js b/js/F8App.js
  19.     index 9443433..ff45131 100644
  20.     --- a/js/F8App.js
  21.     +++ b/js/F8App.js
  22.     @@ -28,7 +28,7 @@
  23.     var React = require('React');
  24.     var AppState = require('AppState');
  25.     var LoginScreen = require('./login/LoginScreen');
  26.     -var PushNotificationsController = require('./PushNotificationsController');
  27.     +//var PushNotificationsController = require('./PushNotificationsController');
  28.     var StyleSheet = require('StyleSheet');
  29.     var F8Navigator = require('F8Navigator');
  30.     var CodePush = require('react-native-code-push');
  31.     @@ -77,9 +77,9 @@ var F8App = React.createClass({
  32.     },

  33.     render: function() {
  34.     - if (!this.props.isLoggedIn) {
  35.     - return <LoginScreen />;
  36.     - }
  37.     + //if (!this.props.isLoggedIn) {
  38.     + // return <LoginScreen />;
  39.     + //}
  40.     return (
  41.     <View style={styles.container}>
  42.     <StatusBar
  43.     @@ -88,7 +88,6 @@ var F8App = React.createClass({
  44.     barStyle="light-content"
  45.     />
  46.     <F8Navigator />
  47.     - <PushNotificationsController />
  48.     </View>
  49.     );
  50.     },
  51.     diff --git a/js/setup.js b/js/setup.js
  52.     index b8134ab..0a0f962 100644
  53.     --- a/js/setup.js
  54.     +++ b/js/setup.js
  55.     @@ -63,9 +63,9 @@ function setup(): ReactClass<{}> {
  56.     };
  57.     }
  58.     render() {
  59.     - if (this.state.isLoading) {
  60.     - return null;
  61.     - }
  62.     + //if (this.state.isLoading) {
  63.     + // return null;
  64.     + //}
  65.     return (
  66.     <Provider store={this.state.store}>
  67.     <F8App />
复制代码


也可以直接把上面的内容保存到 fix_android.patch 文件,放到 f8app 文件夹。然后在 f8app 文件夹运行 git apply fix_android.patch 执行变更。
作者: 悠悠小仙仙    时间: 2017-6-29 17:01
最近也准备接入覆盖率平台,发现我们的应用也开始采用类似rn的框架。。那遇到那种又有native,又有rn的应用,这个覆盖率是不是要分开来看待了?另外,你这个是测试用例的覆盖率,那手动测试的覆盖率怎么做呢?
作者: 巴黎的灯光下    时间: 2017-6-29 17:02


好问题,从技术角度,这两者的覆盖率从收集到生成报告都是两套不同的机制的。如何结合起来一起看,这是个难题。目前还没到这一步,不好回答,不过从我的角度,最终覆盖率的服务形式不是单纯让测试人员看覆盖率报告,而是从覆盖率报告得到测试的推荐建议,例如 xx 模块未覆盖,涉及 xx 流程,优先级 px ,建议通过 xx 用例覆盖。

手动测试的覆盖率是下一步目标,目前计划是用 istanbul-middleware 来做。具体可行性后续继续研究。

作者: jingzizx    时间: 2017-6-29 20:28
目前自己还没开始覆盖的工作,先学习了




欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2