ReactWheels06-01测试覆盖率和持续集成上

什么是工程化

table 小作坊 工程化
代码管理 qq传 git/svn
部署代码 登录机器手动操作 一键部署
代码质量 靠感觉 工具 + review
性能优化 靠感觉 数据上报
需求管理 下周上线 任务管理(排期)

工程化的核心:

  1. 自动化——能用机器做的,绝不给人做(人会犯错)
  2. 工业化——铁打的硬盘,流水的兵(没有任何人是独一无二的,所有人都是可替代的)
    • 为什么有些代码只能一个人改?因为没文档!这是不对的

测试覆盖率

代表着代码质量

  1. 配置
  2. 代码覆盖率 vs 函数覆盖率 vs 分支覆盖率

继续我们的造轮子 先 yarn test 跑通测试

修改 jest.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 修改为 true
collectCoverage:true
# 然后运行 yarn test,运行生成一个表格

-----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files | 95 | 100 | 75 | 100 | |
lib | 100 | 100 | 100 | 100 | |
button.tsx | 100 | 100 | 100 | 100 | |
lib/helpers | 100 | 100 | 100 | 100 | |
classes.tsx | 100 | 100 | 100 | 100 | |
lib/icon | 92.86 | 100 | 50 | 100 | |
icon.tsx | 100 | 100 | 100 | 100 | |
importIcons.js | 75 | 100 | 0 | 100 | |
-----------------|----------|----------|----------|----------|-------------------|
  • stmts 语句覆盖率 100代表所有语句都测试了, 75代表只有75%的语句测试了
  • branch 分支覆盖率
  • funcs 函数覆盖率
  • Lines 行数覆盖率,但这个数字对我们没有实际意义。我们需要再加一个配置,就是告诉我哪一行有问题

再次修改 jest.config.js

1
2
3
4
5
6
7
8
9
10
collectCoverage: true,
reporters: ["default","jest-junit"]


# 运行 yarn test,报错了
Error: Could not resolve a module for a custom reporter.
Module name: jest-junit

# 安装依赖
yarn add --dev jest-junit@5.2.0

再次运行 yarn test

此时我们项目里多了一个文件 ===> junit.xml

内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<testsuites name="jest tests" tests="9" failures="0" time="3.321">
<testsuite name="icon" errors="0" failures="0" skipped="0" timestamp="2019-06-26T14:32:19" time="1.803" tests="2">
<testcase classname="icon render successfully" name="icon render successfully" time="0.025">
</testcase>
<testcase classname="icon onClick" name="icon onClick" time="0.057">
</testcase>
</testsuite>
<testsuite name="button" errors="0" failures="0" skipped="0" timestamp="2019-06-26T14:32:21" time="0.185" tests="1">
<testcase classname="button 是个 div" name="button 是个 div" time="0.002">
</testcase>
</testsuite>
<testsuite name="我的第一个测试用例" errors="0" failures="0" skipped="0" timestamp="2019-06-26T14:32:21" time="0.135" tests="1">
<testcase classname="我的第一个测试用例 1 等于 1" name="我的第一个测试用例 1 等于 1" time="0.002">
</testcase>
</testsuite>
<testsuite name="classes" errors="0" failures="0" skipped="0" timestamp="2019-06-26T14:32:22" time="0.159" tests="5">
<testcase classname="classes 接受 1 个 className" name="classes 接受 1 个 className" time="0.001">
</testcase>
<testcase classname="classes 接受 2 个 className" name="classes 接受 2 个 className" time="0.001">
</testcase>
<testcase classname="classes 接受 undefined 结果不会出现 undefined" name="classes 接受 undefined 结果不会出现 undefined" time="0">
</testcase>
<testcase classname="classes 接受各种奇怪值" name="classes 接受各种奇怪值" time="0.007">
</testcase>
<testcase classname="classes 接受 0 个参数" name="classes 接受 0 个参数" time="0.001">
</testcase>
</testsuite>
</testsuites>

还是不够详细啊!!!

修改 package.json里的 scripts

1
2
3
4
5
6
7
// 先别管那个路径是什么,后面会说
scripts:{
"xxx": "cross-env NODE_ENV=test JEST_JUNIT_OUTPUT=./test-results/jest/results.xml jest --config=jest.config.js"
}

// 运行 yarn xxx
// 此时生成了 test-results/jest/results.xml 的文件内容根上次差不多,还是不够详细

更详细的测试覆盖率

1
2
3
4
5
6
7
collectCoverage: true,
// "lib/**/*.{ts,tsx}" lib里的文件都要测,除了__tests__里的,jest的默认规则是排除有__tests__目录的文件
// "!**/node_modules/**" 任意包含node_modules的目录不测
collectCoverageFrom: ["lib/**/*.{ts,tsx}", "!**/node_modules/**"],
// 生成的报告放在那里
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],

完整版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// https://jestjs.io/docs/en/configuration.html

module.exports = {
verbose: true,
clearMocks: false,
reporters: ["default"],

collectCoverage: true,
collectCoverageFrom: ["lib/**/*.{ts,tsx}", "!**/node_modules/**"],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],

moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
moduleDirectories: ['node_modules'],
globals: {
'ts-jest': {
tsConfig: 'tsconfig.test.json',
},
},
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/file-mock.js",
"\\.(css|less|sass|scss)$": "<rootDir>/test/__mocks__/object-mock.js",
},
testMatch: ['<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)'],
transform: {
"^.+unit\\.(js|jsx)$": "babel-jest",
'^.+\\.(ts|tsx)$': 'ts-jest',
},
setupFilesAfterEnv: ["<rootDir>test/setupTests.js"]
}

运行 yarn xxx,此时多了一个 coverage 目录

点开 coverage/lcov-report/index.html 双击在浏览器打开

就能看到各种详细信息

我们主要看什么

  • 语句覆盖率 (如果很低说明很多代码没测)
  • 分支覆盖率

有的时候一行有多个语句;
有的时候多行组成一个语句;
所以语句覆盖率和行数覆盖率是不同的

持续集成

  1. 比较流行的工具 google 搜 free ci tools
  2. 配置Travis CI
  3. 如何给自己的项目增加小图标

新建一个仓库 react-gulu-test-4

  • 在上次代码的终端里输入
1
2
git remote set-url origin https://github.com/slTrust/react-gulu-test-4.git
git push -u origin master

我们这次使用 circleci

  1. google 搜 circleci
  2. 点击 登录
  3. log in with github
  4. 左侧菜单点击 ADD PROJECTS
  5. 选择linux
  6. 选择你的仓库 我的是 react-gulu-test-4 点击 Set Up Project
  7. 创建一个目录 Add .circleci/config.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    #https://github.com/revolunet/create-react-app-circleci/blob/master/.circleci/config.yml
    defaults: &defaults
    docker:
    # 我们使用 node8来测试
    - image: circleci/node:8
    # circleci的 version
    version: 2
    jobs:
    # 准备阶段
    prepare:
    <<: *defaults
    steps:
    # 迁出代码
    - checkout
    # 根据当前的package.json 创建一个 md5 把它当作key 创建一个缓存,加速之后代码的运行,只要package.json里面没有变化
    # 就可以使用上次的缓存,不用从头安装
    - restore_cache:
    keys:
    - v2-dependencies-{{ checksum "package.json" }}
    - run: yarn install
    - save_cache:
    paths:
    - node_modules
    key: v2-dependencies-{{ checksum "package.json" }}
    - persist_to_workspace:
    root: .
    paths:
    - node_modules
    build:
    <<: *defaults
    steps:
    - checkout
    - attach_workspace:
    at: .
    - run: yarn build
    - persist_to_workspace:
    root: .
    paths:
    - dist
    test:
    <<: *defaults
    steps:
    - checkout
    - attach_workspace:
    at: .
    - run: yarn ci
    - store_test_results:
    path: test-results

    workflows:
    version: 2
    build_accept_deploy:
    jobs:
    - prepare
    - build:
    requires:
    - test
    - test:
    requires:
    - prepare
  8. push 你本地的代码

  9. 点击一下 Start Building
  10. test的过程失败了。 因为 不知道 yarn ci是什么
  11. 修改 package里的 script里的 xxx 改为 ci 再次提交
  12. 测试成功了! 但是无法看到测试覆盖率的详情!
  13. 因为我们少了配置 jest.config.js里
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 添加 jest-junit
    reporters: ["default","jest-junit"],


    // 安装依赖
    yarn add --dev jest-junit@6.3.0

    // 运行 yarn ci
    此时多了一个文件 test-results/jest/results.xml
    这个文件主要是给 ci 看的 不是给人看的
    // 所以它不该被提交 添加在 .gitignore 添加忽略

    再次 push代码

发布代码

修改 package.json

1
2
# 添加如下内容
"files":["/dist"],

如果提示 “没权限”,你就登录一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 记得先切换为默认源
npm adduser xxx
Username: xxx
Password: xxx
Email: (this IS public) xxx
Logged in as almost_00 on https://registry.npmjs.org/.
// 显示登录成功


npm publish
# 如果 还提示没权限 请修改 package.json里的name 和version


# 401 没登录
# 403 没权限

yarn 发布代码

1
2
3
# 只要你登录了
可以直接
yarn publish

测试我们的包

1
2
3
4
5
6
7
8
9
cd ~/Desktop
mkdir aa
cd aa
npm init -y
# npm i 你的包名
npm i fui_t888

发现 node_modules 你的包 dist里是空的 ,原因是 yarn publish 没发布全, 我们package.json 里的 files写的有问题
而我们重新用 npm publish 后代码是好的

修改 package.json

1
"files": ["/dist/**/*"]

再次发布代码

1
2
3
yarn publish

npm publish