51Testing软件测试论坛

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

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

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

[原创] React + Vue + 小程序测试详解

[复制链接]
  • TA的每日心情
    无聊
    3 天前
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2021-1-14 09:47:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    工具
      单元测试要求单元之间没有耦合,每个单元各司其职,这样单元测试就会很明确,增加单元测试,和删除单元测试都比较容易,所以函数式测试风格,可能更加好表达测试...
      单元测试只是手段,即使 100% 覆盖率的单元测试,也不能保证程序是没有 bug 的。
      jest 一个功能齐全的测试 javascript 测试框架, mock、覆盖率、Snapshot快照、执行器、断言库和间谍 spy 功能等等...
      enzyme 是 Airbnb 出品, 是一个工具库,让我们更方便的遍历、操作 React 组件输出的内容
      enzyme-adapter-react-16
      @vue/test-utils Vue 组件测试工具
      miniprogram-simulate 微信小程序的自动化和单元测试
      React 测试
      配置
      enzyme 有众多的组合提供才是
    1. import Enzyme from 'enzyme';
    2. import Adapter from 'enzyme-adapter-react-16';

    3. Enzyme.configure({ adapter: new Adapter() });
    复制代码
    chai-enzyme with Mocha/Chai.
      jasmine-enzyme with Jasmine.
      jest-enzyme with Jest.
      should-enzyme for should.js.
      expect-enzyme for expect.
      官方示例
    1. import React from 'react';
    2. import { expect } from 'chai';
    3. import { shallow } from 'enzyme';
    4. import sinon from 'sinon';

    5. import MyComponent from './MyComponent';
    6. import Foo from './Foo';

    7. describe('<MyComponent />', () => {
    8.   it('renders three <Foo /> components', () => {
    9.     const wrapper = shallow(<MyComponent />);
    10.     expect(wrapper.find(Foo)).to.have.lengthOf(3);
    11.   });

    12.   it('renders an `.icon-star`', () => {
    13.     const wrapper = shallow(<MyComponent />);
    14.     expect(wrapper.find('.icon-star')).to.have.lengthOf(1);
    15.   });

    16.   it('renders children when passed in', () => {
    17.     const wrapper = shallow((
    18.       <MyComponent>
    19.         <div className="unique" />
    20.       </MyComponent>
    21.     ));
    22.     expect(wrapper.contains(<div className="unique" />)).to.equal(true);
    23.   });

    24.   it('simulates click events', () => {
    25.     const onButtonClick = sinon.spy();
    26.     const wrapper = shallow(<Foo onButtonClick={onButtonClick} />);
    27.     wrapper.find('button').simulate('click');
    28.     expect(onButtonClick).to.have.property('callCount', 1);
    29.   });
    30. });
    31. import React from 'react';
    32. import sinon from 'sinon';
    33. import { expect } from 'chai';
    34. import { mount } from 'enzyme';

    35. import Foo from './Foo';

    36. describe('<Foo />', () => {
    37.   it('allows us to set props', () => {
    38.     const wrapper = mount(<Foo bar="baz" />);
    39.     expect(wrapper.props().bar).to.equal('baz');
    40.     wrapper.setProps({ bar: 'foo' });
    41.     expect(wrapper.props().bar).to.equal('foo');
    42.   });

    43.   it('simulates click events', () => {
    44.     const onButtonClick = sinon.spy();
    45.     const wrapper = mount((
    46.       <Foo onButtonClick={onButtonClick} />
    47.     ));
    48.     wrapper.find('button').simulate('click');
    49.     expect(onButtonClick).to.have.property('callCount', 1);
    50.   });

    51.   it('calls componentDidMount', () => {
    52.     sinon.spy(Foo.prototype, 'componentDidMount');
    53.     const wrapper = mount(<Foo />);
    54.     expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
    55.     Foo.prototype.componentDidMount.restore();
    56.   });
    57. });
    复制代码
     Vue 测试
      Vue 中的测试以 @vue/test-utils 作为核心,测试时还需要其他的 npm 包支持,和配置。
      其他的 npm 包
    1. # jest
    2. yarn add jest @vue/test-utils --dev

    3. # vue[url=home.php?mod=space&uid=24198]@next[/url] 选择一个适合版本,现在一般配合 @babel/core, 而非 babel-core
    4. yarn add vue-jest@next --dev

    5. # babel-jest
    6. yarn add babel babel-jest

    7. # jest 默认支持 commonjs 规范,如果我们要使用 esmodule, 那么就需要 babel 的插件转换语法
    8. yarn add @babel/plugin-transform-modules-commonjs --dev
    复制代码
     根据配置,进行相应的配置:
      npm 脚本启动
    1. {
    2.     "scripts": {
    3.         "test": "jest --watch --coverage"
    4.     }
    5. }
    复制代码
     配置 jest
    1. module.exports = {
    2.   collectCoverage: true,
    3.   collectCoverageFrom: [
    4.     "./src/**/*.{js, vue}",
    5.     "!node_modules/**"
    6.   ],
    7.   coverageDirectory: './coverage',
    8.   coveragePathIgnorePatterns: [
    9.     "/node_modules/"
    10.   ],
    11.   coverageReporters: [
    12.     "json",
    13.     "text",
    14.     "lcov",
    15.     "clover"
    16.   ],
    17.   moduleFileExtensions: [
    18.     "js",
    19.     "json",
    20.     "jsx",
    21.     "ts",
    22.     "tsx",
    23.     "node",
    24.     "vue"
    25.   ],
    26.   testMatch: [
    27.     "**/__tests__/**/*.[jt]s?(x)",
    28.     "**/?(*.)+(spec|test).[tj]s?(x)"
    29.   ],
    30.   transform: {
    31.     // 用 `babel-jest` 处理 `*.js` 文件
    32.       "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
    33.       // 用 `vue-jest` 处理 `*.vue` 文件
    34.       ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
    35.   },
    36.   transformIgnorePatterns: [
    37.     "/node_modules/"
    38.   ]
    39. };
    复制代码
    babel 配置
      我们使用 Taro-Next 生成的项,根据测试环境给不同的 babel 配置。
    1. const isTest = process.env.NODE_ENV === 'test'
    2. const presets = []

    3. if (isTest) {
    4.   presets.concat([
    5.     '@babel/preset-env',
    6.     {
    7.       targets: {
    8.         chrome: 52,
    9.       },
    10.     },
    11.   ])
    12. } else {
    13.   presets.concat([
    14.     'taro',
    15.     {
    16.       framework: 'vue',
    17.       ts: false,
    18.     },
    19.   ])
    20. }
    21. module.exports = {
    22.   presets: presets,
    23.   plugins: [
    24.     "@babel/plugin-transform-modules-commonjs"
    25.   ]
    26. }
    复制代码
    基本工作基本完成,接下来就可以快乐的进行测试了。
      基础 API
    mount 挂载组件
      shallowMount 潜挂载组件
      接收一个 Vue 组件作为参数。挂载之后就得到了一个 包裹了 组件 的 wrapper。这个 wrapper 具有访问,操作组件的能力。
      官方示例
    1. <template>
    2.   <span>{{ message }}</span>
    3. </template>

    4. <script>
    5.   export default {
    6.     data () {
    7.       return {
    8.         message: 'hello!'
    9.       }
    10.     },
    11.     created () {
    12.       this.message = 'bye!'
    13.     }
    14.   }
    15. </script>
    16. // 导入 Vue Test Utils 内的 `shallowMount` 和待测试的组件
    17. import { shallowMount } from '@vue/test-utils'
    18. import MyComponent from './MyComponent.vue'

    19. // 挂载这个组件
    20. const wrapper = shallowMount(MyComponent)

    21. // 这里是一些 Jest 的测试,你也可以使用你喜欢的任何断言库或测试
    22. describe('MyComponent', () => {
    23.   // 检查原始组件选项
    24.   it('has a created hook', () => {
    25.     expect(typeof MyComponent.created).toBe('function')
    26.   })

    27.   // 评估原始组件选项中的函数的结果
    28.   it('sets the correct default data', () => {
    29.     expect(typeof MyComponent.data).toBe('function')
    30.     const defaultData = MyComponent.data()
    31.     expect(defaultData.message).toBe('hello!')
    32.   })

    33.   // 检查 mount 中的组件实例
    34.   it('correctly sets the message when created', () => {
    35.     expect(wrapper.vm.$data.message).toBe('bye!')
    36.   })

    37.   // 创建一个实例并检查渲染输出
    38.   it('renders the correct message', () => {
    39.     expect(wrapper.text()).toBe('bye!')
    40.   })
    41. })
    复制代码
      潜挂载组件,调用函数之后,会得到一个 wrapper, wrapper 下面的一些方法是我们需要掌握的。
      wrapper.vm
      wrapper.text()
      wrapper.html() 组件渲染出的 html
      wrapper.contains('xxx') 已包含的元素
      wrapper.find('xxx') 找到元素,模拟用户交互
      测试 Vue Props
      测试 props 和 computed 的默认值。
    1. import { mount } from '@vue/test-utils'
    2. import Icon from '../index'

    3. test('has icon component', () => {
    4.   const wrapper = mount(Icon)
    5.   console.log("name ", wrapper.vm.name)
    6.   expect(wrapper.vm.name).toBe('arrow')
    7.   expect(wrapper.vm.width).toBe("30")
    8.   expect(wrapper.vm.height).toBe("30")
    9.   expect(wrapper.vm.color).toBe("#f00")
    10.   expect(wrapper.vm.iconCls).toBe("van-icon van-icon-arrow")
    11.   expect(wrapper.vm.iconStyle).toEqual({
    12.     width: '30px',
    13.     height: '30px',
    14.     lineHeight: '30px',
    15.     color: '#f00'
    16.   })
    17. })
    复制代码
     组件中 props 包含了 name、width、height、color, 计算属性: iconCls、iconStyles
      测试 props 的传入值
      我们需要给 mount 传入第二个参数,一些选项数据:
    1. test('props custom params', () => {
    2.   const wrapper = mount(Icon, {
    3.     propsData: {
    4.       name: 'good-job-o',
    5.       width: '40',
    6.       height: '40',
    7.       color: 'blue'
    8.     }
    9.   })

    10.   expect(wrapper.props().name).toBe('good-job-o') // 3 passed, 3 total
    11. })
    复制代码
    小程序测试
      小程序可以进行组件级别的单元测试,也可以进行页面级别的测试。
      自定义组件测试
      工具:miniprogram-simulate安装,组件化单元测试,不依赖小程序的运行时。读取组件直接,进行测试。
      注意:小程序的单元测试能力是很有限的。
    1. yarn add miniprogram-simulate --dev
    复制代码
    官方示例
    1. // /test/components/index.test.js
    2. const simulate = require('miniprogram-simulate')

    3. test('components/index', () => {
    4.     const id = simulate.load('/components/index') // 此处必须传入绝对路径
    5.     const comp = simulate.render(id) // 渲染成自定义组件树实例
    6.     const parent = document.createElement('parent-wrapper') // 创建父亲节点
    7.     comp.attach(parent) // attach 到父亲节点上,此时会触发自定义组件的 attached 钩子
    8.     const view = comp.querySelector('.index') // 获取子组件 view
    9.     expect(view.dom.innerHTML).toBe('index.properties') // 测试渲染结果
    10.     expect(window.getComputedStyle(view.dom).color).toBe('green') // 测试渲染结果
    11. })
    复制代码
    页面级别的测试
      页面级别的测试叫小程序自动化,自动化可以帮助完成很多重复的任务。
      打开项目
      编译预览
      上传文件
      ...
      小程序为自动化提供了一个重要的 SDK: miniprogram-automator, 注意和小程序单元测试 miniprogram-simulate 是不一样的。
      miniprogram-automator 需要运行,配置启动微信小程序开发工具。
      官方示例
    1. const automator = require('miniprogram-automator')

    2. automator.launch({
    3.   cliPath: 'path/to/cli', // 工具 cli 位置,如果你没有更改过默认安装位置,可以忽略此项
    4.   projectPath: 'path/to/project', // 项目文件地址
    5. }).then(async miniProgram => {
    6.   const page = await miniProgram.reLaunch('/page/component/index')
    7.   await page.waitFor(500)
    8.   const element = await page.$('.kind-list-item-hd')
    9.   console.log(await element.attribute('class'))
    10.   await element.tap()

    11.   await miniProgram.close()
    12. })
    复制代码














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

    使用道具 举报

    本版积分规则

    关闭

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

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

    GMT+8, 2024-11-24 05:22 , Processed in 0.070318 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

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