测试 
基础概念 
单元测试 
- 优势:快速反馈及时发现bug、改善代码质量及可维护性、check他人代码、活文档
- 最佳写测试时机:
- 通过单测替代手动验证
- 先写测试再实现业务逻辑,TDD敏捷开发
js
// 入门体验
import { expect, it, describe, beforeEach, test } from "vitest";
import { useTodoStore } from './todo';
import { createPinia, setActivePinia } from 'pinia';
describe("todo", () => {
  test("新增一个todo", () => {
    // 1、准备数据
    setActivePinia(createPinia())
    const todoStore = useTodoStore()
    const title = "吃饭"
    // 2、调用
    todoStore.addTodo(title)
    // 3、验证
    expect(todoStore.todos[0].title).toBe(title)
    // 4、重置
    todoStore.reset()
  })
  test("reverse Todo的内容", () => {
    // 1、准备数据
    setActivePinia(createPinia())
    const todoStore = useTodoStore()
    const title = "reverse:HeiHei"
    // 2、调用
    todoStore.addTodo(title)
    // 3、验证
    expect(todoStore.todos[0].title).toBe('ieHieH')
    // 4、重置
    todoStore.reset()
  })
});Vitest 
命令行 
bash
vitest # 在当前目录中启动 Vitest,开发环境默认为监听模式,CI 环境会自动进入运行(run)模式
vitest xxx # xxx替换为路径名,将仅运行路径中包含 xxx 的测试文件
vitest run # 执行单次运行
vitest watch # 运行所有测试套件,监听变化并在变化时重新运行测试【别名:vitest dev】
vitest related # ???
vitest bench # ???
vitest init # ???测试条件 
指定超时阈值 
- 以毫秒为单位,作为第三个参数传递给测试。默认值为 5 秒
ts
import { test,beforeAll  } from 'vitest'
test('name', async () => {
  /* ... */
}, 1000)
beforeAll(async () => {
  /* ... */
}, 1000)
// vitest.config.js
export default defineConfig({
  test: {
    exclude: [],
    testTimeout:5000, // 超时阈值 默认值为 5 秒
  },
})skip跳过测试 
- .skip跳过/避免 运行某些测试套件或测试- skip修饰的测试不会被执行
 
- .skipIf(xxx)('remark 注释',()=>{...})当xxx为true时,跳过测试内容
- .runIf(xxx)('remark 注释',()=>{...})仅当xxx为true时,执行测试内容,与skipIf 相反
ts
import { assert, describe, it } from 'vitest'
describe.skip('skipped suite', () => {
  it('test', () => {
    // 已跳过此测试套件,无错误
    assert.equal(Math.sqrt(4), 3)
  })
})
describe('suite', () => {
  it.skip('skipped test', () => {
    // 已跳过此测试,无错误
    assert.equal(Math.sqrt(4), 3)
  })
})only选择测试 
- .only仅运行某些测试套件或测试
- 存在only修饰后,未被修饰的测试不会被执行
ts
import { assert, describe, it } from 'vitest'
// 仅运行此测试套件(以及标记为 Only 的其他测试套件)
describe.only('suite', () => {
  it('test', () => {
    assert.equal(Math.sqrt(4), 3)
  })
})
describe('another suite', () => {
  it('skipped test', () => {
    // 已跳过测试,因为测试在 Only 模式下运行
    assert.equal(Math.sqrt(4), 3)
  })
  it.only('test', () => {
    // 仅运行此测试(以及标记为 Only 的其他测试)
    assert.equal(Math.sqrt(4), 2)
  })
})concurrent并发测试 
- 会将所有测试标记为并发测试,包含深层级
- 快照和断言必须使用本地测试上下文中的 expect,以确保检测到正确的测试
ts
import { describe, test } from 'vitest'
// 此测试套件中的所有测试套件和测试将并行运行。
describe.concurrent('suite', () => {
  test('concurrent test 1', async () => {
    /* ... */
  })
  describe('concurrent suite 2', async () => {
    test('concurrent test inner 1', async () => {
      /* ... */
    })
    test('concurrent test inner 2', async () => {
      /* ... */
    })
  })
  test.concurrent('concurrent test 3', async () => {
    /* ... */
  })
})sequential顺序测试 
- 将每个测试标记为顺序测试,在concurrent并发测试中非常有用
ts
import { describe, test } from 'vitest'
describe.concurrent('suite', () => {
  test('concurrent test 1', async () => {
    /* ... */
  })
  test('concurrent test 2', async () => {
    /* ... */
  })
  describe.sequential('', () => {
    test('sequential test 1', async () => {
      /* ... */
    })
    test('sequential test 2', async () => {
      /* ... */
    })
  })
})shuffle随机测试 
- 以随机顺序运行所有测试的方法
- 配置选项 / 方法
ts
import { describe, test } from 'vitest'
// 或 `describe('suite', { shuffle: true }, ...)`
describe.shuffle('suite', () => {
  test('random test 1', async () => {
    /* ... */
  })
  test('random test 2', async () => {
    /* ... */
  })
  test('random test 3', async () => {
    /* ... */
  })
  // `shuffle` 是继承的
  describe('still random', () => {
    test('random 4.1', async () => {
      /* ... */
    })
    test('random 4.2', async () => {
      /* ... */
    })
  })
  // 禁用内部的 shuffle
  describe('not random', { shuffle: false }, () => {
    test('in order 5.1', async () => {
      /* ... */
    })
    test('in order 5.2', async () => {
      /* ... */
    })
  })
})
// 顺序取决于配置中的 `sequence.seed` 选项(默认为 `Date.now()`)for/each 循环测试 
js
import { describe, expect, test } from 'vitest'
import { urlParamsToObj } from '../index.ts'
describe("urlParamsToObj", () => {
  test.each([
    { i: '?foo=bar&baz=qux', o: { foo: "bar", baz: "qux" } },
    { i: '', o: {} },
    { i: '?foo=bar', o: { foo: "bar" } }
  ])('$i', ({ i, o }) => {
    expect(urlParamsToObj(i)).toEqual(o);
  });
})todo未实现测试 
- .todo留存将要实施的测试套件和测试的待办事项- 在测试过程输出测试过程,TODO的提示
 
ts
import { describe, it } from 'vitest'
// 此测试套件的报告中将显示一个条目
describe.todo('unimplemented suite')
// 此测试的报告中将显示一个条目
describe('suite', () => {
  it.todo('unimplemented test')
})API 
test 
定义了一组相关的期望,接收测试名称和保存测试期望的函数
- 类型: (name: string | Function, fn: TestFunction, timeout?: number | TestOptions) => void
- 可提供超时(毫秒)时间。 默认5s,可以通过 testTimeout 进行全局配置
js
import { expect, test } from 'vitest'
test('should work as expected', () => {
  expect(Math.sqrt(4)).toBe(2)
})describe 
在当前上下文中定义一个新的测试套件,作为一组相关测试或基准以及其他嵌套测试套件,包裹多项测试内容,使呈现的结果更有层次
ts
import { describe, expect, test } from 'vitest'
function numberToCurrency(value: number | string) {
  if (typeof value !== 'number') {
    throw new TypeError('Value must be a number')
  }
  return value
    .toFixed(2)
    .toString()
    .replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
describe('numberToCurrency', () => {
  describe('given an invalid number', () => {
    test('composed of non-numbers to throw error', () => {
      expect(() => numberToCurrency('abc')).toThrowError()
    })
  })
  describe('given a valid number', () => {
    test('returns the correct currency format', () => {
      expect(numberToCurrency(10000)).toBe('10,000.00')
    })
  })
})expect 
用于创建断言,期待什么得到什么
- soft 断言失败时,继续运行并将失败标记为测试失败,直到测试完成
- poll 重新运行直到成功为止,可设置 interval和timeout选项来配置 重新运行的次数
- toBe 表示全等,若是对象函数等,应表示它们的引用一致
- toEqual 用于比较对象的内容是否一致,而不考虑它们的引用指针
- toBeFalsy 判断内容是否转化为false,而不关心具体的值时 - 除了 false、null、undefined、NaN、0、-0、0n、""和document.all以外,JavaScript 中的一切都是true
 
- 除了 
- toContain 判断数组/字符串是否包含指定的内容
- toThrowError 在被调用时是否会抛出对应的错误
ts
import { expect, test } from 'vitest'
// soft 失败时继续运行并标记为失败
test('expect.soft test', () => {
  expect.soft(1 + 1).toBe(3) // mark the test as fail and continue
  expect(1 + 2).toBe(4) // failed and terminate the test, all previous errors will be output
  expect.soft(1 + 3).toBe(5) // do not run
})
// toBeFalsy 判断返回值是否为false 
import { Stocks } from './stocks.js'
const stocks = new Stocks()
test('if Bill stock hasn\'t failed, sell apples to him', () => {
  stocks.syncStocks('Bill')
  expect(stocks.stockFailed('Bill')).toBeFalsy()
})
// toContain 是否包含指定的内容
import { getAllFruits } from './stocks.js'
test('the fruit list contains orange', () => {
  expect(getAllFruits()).toContain('orange')
  const element = document.querySelector('#el')
  // element has a class
  expect(element.classList).toContain('flex')
  // element is inside another one
  expect(document.querySelector('#wrapper')).toContain(element)
})
// toThrow 判断函数内部是否抛出对应的错误
test("toThrow", () => {
  function getName(name) {
    if (typeof name !== "string") {
      throw new Error("错误的name");
    }
    return "hei";
  }
  expect(() => { getName(111); }).toThrow("错误的name");
});生命周期 
- 执行顺序: beforeAll>beforeEach>test>afterEach>afterAll
