• 1
  • 1
分享

       以前,写完一段代码我也是直接调用或者实例化一下,发现过了就把测试相关部分删了。今年的不幸与坎坷使我有很长一段时间去思考人生,不想将就了,鲁棒健壮的程序,开发和测试应该是分得很开的,于是我选择jest去做单元测试这件事。

       为什么要做单元测试

       在开始之前,我们先思考这样一个问题,我们为什么要做单元测试?

       不扯犊子直接说吧,第一点,用数据、用茫茫多的测试用例去告诉使用者,你的程序是多么鲁棒健壮;第二点,把它作为一种素养去培养吧,当你按照一系列规范去做事,那么你做出来的东西,我想是有品质在的。

       jest的安装

       在确保你的电脑装有node环境的情况下,我们通过

mkdir jest-study && cd jest-study

       来初始化项目,然后我们通过npm init -y初始化npm环境。

       执行

npm i jest babel-jest @babel/core @babel/preset-env

       命令安装相应的依赖包,因为后面的例子是基于ES Module的语法编写的,所有需要安装babel进行语法转义。当然你也可以选择直接用CommonJS的写法,node天然支持的。

       jest的相关配置

       package.json中相关scripts

       这里笔者罗列了常用的通用的一些关于jest的脚本,后面测试结果会陆续补充一些测试脚本,以上的脚本都编写在package.json文件下的scripts脚本下面。

       通用写法

"test": "jest" : 这个比较傻瓜式,当执行npm run test这条命令是会去对test目录下的所有文件进行相应的jest测试。
"test:help": "jest --help": 顾名思义,如果你不想全局安装jest,又想看看到底有哪些cli命令的话,就它了。
"test:debug": "jest --debug": 顾名思义,debug啊。
"test:verbose": "jest --verbose": 以层级显示地方式在控制台展示测试结果。
"test:noCache": "jest --no-cache": 顾名思义,就是设置有没有缓存,有缓存的话会快点。
"test:init": "jest --init": 执行这句就是在根目录创建一个jest.config.js文件,它在创建的时候有很多选择项给你的。
"test:caculator": "jest ./test/caculator.test.js": 单文件测试。
"test:caculator:watch": "jest ./test/caculator.test.js --watch": 单文件监视测试
"test:watchAll": "jest --watchAll": 监视所有文件改动,测试相应的测试。

       大致基础类的脚本测试就总结到这里,接下来我们看下jest.config.js的相关配置。

       jest.config.js中相关配置

       里面配置的参数太多了,有些配置了以后就可以不再package.json文件下写相应的脚本,这里笔者阉割一部分,列举最常见的几个。

module.exports = {
  // Automatically clear mock calls and instances between every test
  clearMocks: true,
  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",
  // The test environment that will be used for testing
  testEnvironment: "node",
}

       babel相关配置

{
  "presets": [["@babel/preset-env", {
      "targets": {
        "node": "current"
      }
    }
  ]]
}

       这里就是配置了相应的语法转换预设,如果后期有其他需求,可以通过plugins去配置写补丁转义器,相关内容这里就不做介绍了,可以看下笔者之前写的关于babel的文章。

       测试结果

       考虑到把相关信息打在控制台上,第一,控制台可能会出现一处的情况;第二,在查看结果内容多的话可能引起眼睛不适,所有就有了楼下几种可能。

       测试覆盖率

       在package.json中的scripts下配置"test:coverage": "jest --coverage"后,然后执行相应脚本,就会在根目录输出一个coverage文件夹,里面包含了相应的测试脚本。当然控制台也会输出的。

图1.png

       html显示

       执行 npm i jest-html-reporter安装这个模块包(这里提及一下,在npm版本大于5.x以后,可以默认不加--save这种参数),然后在jest.config.js中配置如下:

reporters: [
    "default",
    ["./node_modules/jest-html-reporter", {
      "pageTitle": "Test Report"
    }]
  ],

       执行相关的jest测试后,会在根目录生成一个test-report.html文件,打开后形如:

图2.png

       json显示

       在package.json中配置scripts脚本"test:exportJson": "jest --json --outputFile=./export/reporter.json",然后执行npm run test:exportJson就会输出相应的json报告文件,控制台也会以json的形式输出相应信息。

       断言(expect)

       断言库的种类有很多,例如、assert、should、expect、chai等等,楼下的例子,笔者均以expect作为讲解。

       not

       先说个最简单的expect(received).not.toBe(expected),这句话的意思就是表示否对,表示我断言、接收值不等于期望值。

       toBe(expected)

       这个API常用于断言,值类型的期望值,也就是boolean、string、number、这些类型的,用它做引用类型的断言是不合适也不可取的。

to_be.test.js
describe('#toBe', () => {
  it('to be string', () => {
    expect('hello world').toBe('hello world')
  })
  it('to be number', () => {
    expect(1 + 1).toBe(2)
  })
  it('to be boolean', () => {
    expect(true).toBe(true)
    expect(false).toBe(false)
  })
  it('to be null', () => {
    expect(null).toBe(null)
  })
  it('to be undefined', () => {
    expect(undefined).toBe(undefined)
  })
})

       toEqual(expected)

       通俗的理解就是等于, 可以是值类型也可以是引用类型的相等。

to_equal.test.js
test('#toEqual', () => {
  expect('hello world').toEqual('hello world')
  expect(110).toEqual(110)
  expect(true).toEqual(true)
  expect(false).toEqual(false)
  expect(null).toEqual(null)
  expect(undefined).toEqual(undefined)
  expect([1, 2, 3, 4]).toEqual([1, 2, 3, 4])
  expect({ name: 'ataola' }).toEqual({ name: 'ataola' })
})

       toContain(expected) && toContainEqual(expected)

       toContain()跟的期望值是值类型的,而toContainEqual() `可以是值类型也可以是引用类型,表示包含。


to_contain.test.js
test('#toContain', () => {
  expect([1, 2, 3, 4]).toContain(1)
  expect([[1, 2], [3, 4], [5, 6]]).toContainEqual([1, 2])
})

       数值比较

       楼下expect后面跟的英语的字面量意思就是其方法的作用,分别是,大于、大于等于、小于、小于等于、相似于(接近于),这里值得一题的事最后一个toBeCloseTo(),思考一下改成toBe()可以吗?很显然不行,其算出来的结果是0.30000000000000004,究其原因是js采用的是双精度浮点表示。

number_compare.test.js
test('number compare', () => {
  expect(3).toBeGreaterThan(2)
  expect(3).toBeGreaterThanOrEqual(2.5)
  expect(3).toBeLessThan(4)
  expect(3).toBeLessThanOrEqual(3.5)
  expect(0.1 + 0.2).toBeCloseTo(0.3) // <0.05 passed
})

       toMatch(expected)

       顾名思义,字符串匹配,它支持字符串和正则,/^(\w+)\1+$/匹配的是一个字符串可以由其字串通过n次组合而成的字串(leetcode一道题目),所有其匹配到的是tao。

string_match.test.js
test('string match', () => {
  expect('ataola').toMatch('ataola')
  expect('ataola').not.toMatch('aloata')
  expect('taotao'.match(/^(\w+)\1+$/)[0]).toMatch('tao')
})

       内置的一些基本类型值

       null、undefined、真假值比较特殊,所有这里单独有个方法表示它们。

truthiness.test.js
// toBeNull、 toBeUndefined 、 toBeDefined 、 toBeTruthy、 toBeFalsy
test('truthiness', () => {
  expect(null).toBeNull()
  expect(undefined).toBeUndefined()
  expect('i am defined').toBeDefined()
  expect(true).toBeTruthy()
  expect(false).toBeFalsy()
})

       ToThrow(expected)

       这里是处理相关异常的, 后面可以什么都不根,也可以跟个Error,或者相应的Error输出信息

exceptions.test.js
function gaoError() {
  throw new Error('二营长开炮,开炮,开炮。。。')
}
test('#ToThrow', () => {
  expect(gaoError).toThrow()
  expect(gaoError).toThrow(Error)
  expect(gaoError).toThrow('二营长开炮,开炮,开炮。。。')
})

       好了,到这里比较基础和通用的API就介绍到这里。接下来,我们通过自己编写相关代码去巩固下楼上的知识,这里笔者提供两个demo,一个是关于异步获取数据的断言、一个是实现一个计算器类的断言。

       异步

我们通过request-promise这个库去请求https://v1.hitokoto.cn去获取相应的json数据,然后进行断言。

hitokoto.js
import rp from 'request-promise'
const getHitokoto = async () => {
  const res = await rp('https://v1.hitokoto.cn')
  return res
}
export default getHitokoto
hitokoto.test.js
import getHitokoto from '../src/hitokoto'
test('hitokoto', async () => {
  const data = await getHitokoto()
  expect(data).not.toBeNull()
})

       这里就意思下,读者可以把data里面的数据解构出来,进行相应的断言。

       计算器

       这里模拟了笔者手机上的计算器,实现了加减乘除清零计算等功能。

caculator.js
class CaCulator {
  constructor() {
    this.result = 0
  }
  add(...args) {
    let { result } = this
    result += args.reduce((pre, cur) => pre + cur)
    this.result = result
    return this
  }
  reduce(...args) {
    let { result } = this
    result -= args.reduce((pre, cur) => pre + cur)
    this.result = result
    return this
  }
  multiply(...args) {
    let { result } = this
    if (result) {
      for (const val of args) {
        result *= val
      }
    } 
    this.result = result
    return this
  }
  divide(...args)  {
    let { result } = this
    const has_zero = args.some(item => item === 0)
    if (has_zero) {
      result = '数学体育老师教的吗?'
    } else {
      for (const val of args) {
        result /= val
      }
    }
    this.result = result
    return this
  }
  clear() {
    this.result = 0
    return this
  }
  exec() {
    const { result } = this
    if (typeof result === 'string') {
      this.result = 0
    }
    return result
  }
  init(n) {
    this.result = typeof n === 'number' ? n : 0
    return this
  }
}
export default CaCulator
caculator.test.js
import Caculator from '../src/caculator'
const caculator = new Caculator()
describe('test Caculator', () => {
  test('#add', () => {
    expect(caculator.add(1).exec()).toBe(1)
    expect(caculator.clear().add(1, 2, 3).exec()).toBe(6)
    caculator.clear()
  })
  
  test('#reduce', () => {
    expect(caculator.reduce(1).exec()).toBe(-1)
    expect(caculator.clear().reduce(1, 2, 3).exec()).toBe(-6)
    caculator.clear()
  })
  
  test('#multiply', () => {
    expect(caculator.multiply(1).exec()).toBe(0)
    expect(caculator.init(1).multiply(2, 3, 4, 5).exec()).toBe(120)
    caculator.clear()
  })
  
  test('#divied', () => {
    expect(caculator.divide(0).exec()).toBe('数学体育老师教的吗?')
    expect(caculator.divide(1, 2).exec()).toBe(0)
    expect(caculator.init(100).divide(2, 2).exec()).toBe(25)
  })
})

       这里笔者只是罗列了日常开发中常用的断言API,具体的还是要参见官方文档这样的一手资料,希望能起到抛砖引玉的效果。


作者:丰臣正一

原文链接:https://www.cnblogs.com/cnroadbridge/p/13524099.html

  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          •   之前进行接口测试一直用印度的postman,后来发现一款国产神器ApiPost,完全可以秒杀postman这个印度货。必须安利一波!  1、可以便捷的生成格式规范的文档  记得以前当程序员的时候,每次写接口,基本都是自己大概一写,然后api地址和大致参数发群里就完事儿,剩下全靠前端猜,实在猜不出来了就喊两声:xx,那个yy参数是啥意思?  ApiPost的出现,解决了这个问题:很便捷的生成接口文档。而且更重要的是,这款软件生成的接口文档基本非常规范,并且支持多种格式。如下图:  这就是ApiPost越来越受欢迎的原因之一:可以便捷的生成格式规范的文档。  2、方便规范化开发文档的管理  后...
            0 0 1220
            分享
          •   12月9日,京东创始人刘强东在内网发文回应了一位京东员工发布的帖子,第一财经记者向京东内部员工确认了该回复的真实性。  在回复中,刘强东表示,“我们天天说客户为先,可是工作中处处以自己为中心进行思考!我们经常说战斗只做第一,但是却处处防守,从不想着如何主动出击!很多人天天说创新,却每天就是抄袭跟随别人。出现这么多问题,当然都是我管理不善,我非常自责,但是无论如何,我不会躺平,也希望兄弟们不会躺平。”  刘强东称,“现在组织庞大臃肿低效,改变起来确实需要时间。”  同时,他还对团队提出了期许,“京东基础依然在,相信我们一定会走出低谷。任何一个人任何一家公司都会经历若干个顶峰和低谷才能成就伟大...
            0 0 978
            分享
          •   小B是某业务方向的QA(Quality Assurance Engineer,质量保障工程师)负责人,该方向共3名QA同学,按双周对齐需求测试进展时发现,该方向有多个需求提测后需要等待几天时间,QA同学才能介入测试。虽然出现这种情况,跟该方向近期的需求数量变多有直接关系,但依然有两个可持续的改进方向:需求测试效率的进一步提升;部分需求应推动RD(Research and Development Engineer,研发工程师)自测,实行QA免测。  小D是该方向的一名QA,工作3年左右,对于这两个改进方向,他能理解,但也有一点困惑。需求测试效率提升很容易理解,因为效率提升后,QA资源能够尽快...
            0 0 664
            分享
          •   测试用例(Test Case)是为特定的目的而设计的一组测试输入、执行条件和预期结果的文档。它的作用其实就是为了测试是否满足某个特定需求。测试用例是指导测试工作进行的依据。  测试用例的组成  标准的测试用例通常由以下几个模块组成:  ·用例编号:测试用例的唯一标识。  · 模块:标明被测需求具体属于哪个模块,主要为了更好识别以及维护用例。  · 用例标题:又称之为测试点,就是用一句话来描述测试用例的关注点。每一条用例对应一个测试目的。  · 优先级:根据需求的优先级别来定义。高优先级要覆盖核心业务,重要特性以及使用频率比较高的部分。  · 前提条件...
            0 0 902
            分享
          •   JMeter是Apache软件基金会的开源项目,主要来做功能和性能测试,用Java编写。  我们一般都会用JMeter在本地进行测试,但是受到单个电脑的性能影响,往往达不到性能测试的要求,无法有效的模拟高并发的场景,那么这个时候,我们就可以借由JMeter提供的Romote Test来进行远程的测试。  其工作方式入下图:  我们可以在多台电脑上,启动JMeter的Romote Testing模式,然后用某一台服务器作为Master端通过RMI控制Slave端来执行我们的测试脚本。当JMeter Slave端执行完测试脚本后,会将执行结果发送回Master控制端进行汇总,得出整体的测试报表...
            0 0 786
            分享
      • 51testing软件测试圈微信