自动化测试-进阶知识

2021/8/1 自动化测试
// Test Driven Development (TDD) 测试驱动开发
1. TDD 的开发流程(Red-Green Development)// 测试用例由红变绿的开发流程
  1. 编写测试用例
  2. 运行测试,测试用例无法通过测试
  3. 编写代码,使测试用例通过测试
  4. 优化代码,完成开发
  5. 新增功能则重复上述步骤
2. TDD 的优势
  1. 长期减少回归 Bug
  2. 代码质量更好(组织、可维护性)
  3. 测试覆盖率高
  4. 错误测试代码不容易出现
----------------------------------------------------------------------------------------------
// Behavior Droven Developmen(BDD)行为驱动开发
1. TDDBDD 的区别
  1. TDD
    1. 先写测试再写代码
    2. 一般结合单元测试使用,是白盒测试
    3. 测试重点在代码
    4. 用户安全感低
    5. 速度快
  2. BDD
    1. 先写代码再写测试
    2. 一般结合集成测试使用,是黑盒测试
    3. 测试重点在 UIDOM4. 用户安全感高
    5. 速度慢
----------------------------------------------------------------------------------------------
// Vue 中的 TDD (vue-test-utils)
1. 使用 VueCli 工具生成的脚手架如果有选择测试,则会自带了 vue-test-utils 这个依赖
2. 里面有 shallowMount、mount 等一系列便捷的 API 便于书写 vue 的测试用例,且会返回一个 wrapper
   其中也包含很多 API,具体可查看官网
3. 示例
  import { shallowMount } from 'vue-test-utils'
  import Hello from './hello'
  describe('Hello.vue', () => {
    it('测试 vue 组件', () => {
      const msg = 'chenj'
      // shallowMount:用于单元测试,只会渲染本身,而不会渲染子组件
      // mount:用于集成测试,会渲染本身和子组件
      const wrapper = shallowMount(Hello, {
        propsData: { msg }
      })
      expect(wrapper.text()).toMatch(msg) // wrapper.text 可获取组件的文本
      expect(wrapper.find('.container').length).toBe(1) // wrapper.find 查找
    })
  })
----------------------------------------------------------------------------------------------
// React 中的 TDD (enzyme + enzyme-adapter-react-16)
1. 使用 create-react-app 工具生成的脚手架已经集成了 Jest,在测试 DOM 操作时我们可以自己安装
   npm i enzyme enzyme-adapter-react-16 --save-dev 来使用更便捷的 API 和 vue-test-utils 类似
2. 示例
  import React from 'react'
  import Enzyme, { shallow } from 'enzyme'
  import Adapter from 'enzyme-adapter-react-16'
  import Hello from './hello'
  Enzyme.configure({ adapter: new Adapter() })
  describe('Hello.vue', () => {
    it('测试 react 组件', () => {
      const wrapper = shallow(<Hello />)
      expect(wrapper.find('.container').length).toBe(1) // wrapper.find 查找
      expect(wrapper.find('.container').prop('name')).toBe('chenj') // wrapper.prop 属性
    })
  })
----------------------------------------------------------------------------------------------
// supertest 接口测试
1. 本地开发时使用 jest + supertest 进行接口测试
2. 上线后使用 jest + axios 进行接口测试
----------------------------------------------------------------------------------------------
// 单元测试最佳实践
1. 测试与测试之间应该互相不依赖,不依赖顺序
    1. 编写测试的时候,我们要尽量避免测试与测试之间产生联系,最好是隔离的,因为一旦有所依赖,一个测试的失败将会影响到另一个测试的结果。这样,我们就很难去知道到底是测试失败了是另一个测试所引发的问题,还是因为我们项目的功能不符合预期所造成的问题,而且可能它是在某些场景才失败,也加大了我们调试的难度
2. 一个测试只测试一个功能
    1. 前面我们介绍过,单元测试测试的是系统中的基础工作单元,而工作单元指的是调用某一个系统的方法所产生的结果,所以最好的单元测试应该是一次只测试一个功能,这个功能可能是一个方法的调用,也可能是某个特殊的配置所造成的另一种预期的结果,反正我们就是要根据实际的情况进行细粒度的功能拆分
3. 测试代码应该尽量简单
    1. 为了增加测试代码的可读性,每一个测试套件里面的测试应该尽量保持简单,既增加可读性,也降低编写的测试代码出bug的几率。如果我们的功能没问题,因为测试代码写的过于复杂导致测试失败,也很难调试。为了保持测试尽量简单,我们需要遵守一些原则。第一,尽量不要在一个测试中加入 ifforswitch 等语句,如果你的某个测试中需要用到这样的语句,你应该考虑抽出一些 helper 函数来简化测试。第二,每个测试的代码行数应该尽量控制在十行以内,这样方便他人阅读,也降低测试代码出错的几率
4. 属于同一个测试文件中的测试尽量使用describe包裹起来
    1. 当然,这一点没有那么重要,但是我觉得为了使我们的测试更加可读,使用 describe 包裹起来,会更加符合最佳实践。 describe 第一个参数的描述可以让我们知道该测试套件的作用,一个文件中的所有测试应该就是一个整体,属于同一个测试套件,如果全部将test 暴露在外面,我看到的时候可能会思考,难道这些测试都是独立的,它们之间没有任何关系?但是按道理来说,放在同一个测试文件中的测试,它们应该属于同一个测试套件,如果你的文件中出现两个测试套件,你应该就考虑将它们放在不同的文件中
----------------------------------------------------------------------------------------------
// 单元测试为何难以落实?
1. 单元测试,往往说起来都点头称赞,但做起来都犯难
2. 使用方式不合理
    1. 真正原因:混滑了单元测试和集成测试,导致单元测试代码中有太多 Mock!!! 如,需要服务器启动才能执行的代码,就不是单元测试了
        1. 单元测试,是针对一个单元,即单一的功能
        2. 单元测试,是针对一段逻辑,有 if.else for 逻辑的,平铺直叙的代码不用测试
    2. 如果单元没有被拆分,则拆分出来,再做单元测试。正好,这也是非常好的代码重构
    3. 摘抄一段《聊聊架构》里的代码
    ```java
    // 改造之前的。不容易进行单元测试,因为有 request ,需要启动 http 服务,单元测试就需要 Mock
    public OrderDTO getUserOrder(HttpRequest request) {
        String userId = request,getParameter("userId");
        String orderId = request.getParameter("orderId");
        UserDTO user = userManager.getUser(userId);
        OrderDTO order = orderManager.getOrder(orderId);
        if (order != null && order.getUserId != null && order.getuserId,eguals(userId)) {
            (order.setUser(user);
            return order;
            return null;
        }
    }

    // -------分割线-------

    // 平铺直叙的代码不需要单元测试
    public OrderDTO getUserOrder(HttpRequest request) {
        String userId = request.getParameter("userId");
        String orderId = request.getParameter("orderId");
        UserDTO user = userManager.getUser(userId);
        OrderDTO order = orderManager.getOrder(orderId);
        return checkUser(order,user,userId);
    }

    // 逻辑单元,单独抽离出来,测试输入输出即可,不用 Mock
    public OrderDTO checkUser(OrderDTO order,UserDTO user,String userId) {
        if (order != null && order.getuserId != null && order.getUserId.eguals(userId)) {
            order.setUser(user);
            return order;
        }
        return null;
    }
    ```
3. 另一个现状
    1. 除了以上代码和技术方面的原因,阻碍单元测试开展的还有另外的原因:研发流程不规范
        1. 研发团队和测试团队分离
        2. 如果项目延期,测试团队就抱怨提测延期了,测试时间不够
        3. 研发团队也想多一事不如少一事,反正有人做测试,也不用我们写单元测试了
    2. 上述情况,有工作经验的人相信很熟悉。这种情况,没有太好的解决办法,只能规范研发流程,规范各个流程的产出。这也是架构师的职责之一
    3. 所以,很多现代公司也在做组织架构的转型,特别是现代互联网公司。
        1. 以不信任软件工程师为基础。传统软件公司、大公司多采用。特点是:文档规范详细,具有较大规模的测试团队,研发流程正规且冗长。耶责分工明确,程序员就照着文档写代码即可,写完了就交付测试。
        2. 以信任软件工程师为基础。现代互联网中下公司多采用,推荐程序员去理解业务,去承担测试。这样文档简单,执行效率高。但是要求团必须敏捷管理,高效沟通,以及要求程序员要了解业务
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
Last Updated: 2024/7/31 12:57:25
    飘向北方
    那吾克热-NW,尤长靖