iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0

這篇我們要來幫 Effect 的程式寫測試,現在寫測試大家應該都是以 vitest 為主了吧,我們就來用 vitest 吧,若你平常有在寫測試,像這樣的測試應該不陌生吧

import { it, expect } from 'vitest'

it('work', () => {
  expect(1 + 1).toBe(2)
})

那如果換成測試 Effect 的 function 呢?我們可以有兩種做法,一種是等到 Effect 跑完我們再來測試

import { it, expect } from 'vitest'

// demo 用,實務上將這種簡單的 function 包成 Effect 其實沒太大的幫助
function add(a: number, b: number) {
  return Effect.sync(() => a + b)
}

it('work', () => {
  expect(Effect.runSync(add(1, 1))).toBe(2)
})

另一種是在 Effect 中進行測試

it('work', () => {
  return pipe(
    // 這邊使用 `Effect.gen` 比較方便
    Effect.gen(function* () {
      expect(yield* add(1, 1)).toBe(2)
    }),
    Effect.runPromise,
  )
})

使用 @effect/vitest

Effect 官方知道每次都要這樣寫太麻煩了,所以就推出了 @effect/vitest@effect/vitest 有提供一個 it 的 function ,這個 function 跟 vitest 的很像,不過這邊的 it 還特別多加入了一些測試 Effect 用的 function ,最基本的就是 it.effect

// 注意這邊要是一個回傳 Effect 的 function
it.effect('work', () =>
  Effect.gen(function* () {
    expect(yield* add(1, 1)).toBe(2)
  }),
)

跟上面自己寫的比起來是不是簡單多了

it.prop 實作 property test

@effect/vitest 跟 Effect 的套件中,還包含了一樣東西,這個可以說是 Effect 的主要套件 effect 少數相依的套件 fast-check ,fast-check 是個在做 property test 的套件,這種測試簡單來說,就是產生各式各樣可能的輸入,並且測試你的程式是否可以正常處理,這在實作可能被輸入任意資料的系統而言,是個很有用的測試方式

我們先來看最簡單的做法

import { FastCheck } from 'effect'

it.prop('can handle any number', [FastCheck.integer()], ([a]) => {
  console.log(a)
  expect(a).toBeTypeOf('number')
})

像這樣,如果你看 console 的輸出,你會發現這段程式被用一大堆的亂數跑過了一次

話說你可能會覺得奇怪,我們不是在講 Effect 嗎?怎麼這個測試不需要用到 Effect ,當然,因為 it.prop 就只是重新 export 原始的 fast-check 的 API 而已,實際上有使用到 Effect 的 property test 在 it.effect.prop ,我們可以用它驗證我們之前寫的 add

it.effect.prop('can add any number', [FastCheck.integer(), FastCheck.integer()], ([a, b]) =>
  Effect.gen(function* () {
    const res = yield* add(a, b)
    // 驗證結果是否合理
    expect(res).toBe(a + b)
  }),
)

這次因為輸出的規則很明確,我們可以直接在測試中驗證輸出是否是正確的,也有的時候輸出的規則可能不是那麼的容易,因此我們在做 property test 時,可能有幾種不同的檢查方式

  • 驗證轉換後是否可以被反向轉換,例如加密後解密結果應該要一樣
  • 驗證資料是否在合理範圍內,例如 hash 的輸出
  • 單純驗證處理各式各樣的資料是否有可能 crash

另外你可能還有聽過跟 property test 很像的 fuzzy test ,這兩個其實有點不同,簡單而言 property test 需要知道程式的實作細節,才可以驗證你的輸入與輸出是否合理, fuzzy test 相較之下是黑箱測試,也就是在不知道實作細節的情況下進行測試 (ref)

補充: 使用 addEqualityTesters 比較自訂型態

各位在使用 vitest 時應該常用到 expect(...).toEqual() ,這個 function 可以比較兩個東西的內容是否是一樣的

// toBe 是單純的 Object.is 的比較
it.fails('toBe is not deep equal', () => {
  const obj = {
    hello: 'world',
  }

  expect(obj).toBe({ hello: 'world' })
})

// toEqual 是完整的比較兩個值
it('toEqual is deep equal', () => {
  const obj = {
    hello: 'world',
  }
  
  expect(obj).toEqual({ hello: 'world' })
})

補充一下: it.fails 可以用來表示一個應該要產生錯誤的測試,也就是說如果裡面的程式沒出任何的錯,反而會導致測試失敗

不過 Effect 中有不少自訂的資料型態,像我們之前碰到的 Either 就是其中一個,如果要用 toEqual 比較兩個 Either 是否一樣會是可行的嗎?我們可以簡單的試一下

it('toEqual can test Either?', () => {
  expect(Either.right({ hello: 'world' })).toStrictEqual(Either.right({ hello: 'world' }))
})

其實會發現意外的可以,toEqual 太強了,不過 @effect/vitest 還是有提供一個 addEqualityTesters 的 function 可以確保這些資料可以被用 toEqual 比較,只需要在 vitest 的 setup file 裡呼叫就行

import { addEqualityTesters } from '@effect/vitest'

addEqualityTesters()

我嘗試了幾個內建的自訂型態,還沒有找到需要特別加這個才有辦法正確比較的,就當作是一個補充吧

下一篇要來看 DI 怎麼幫助我們更好的寫測試

Reference


上一篇
12. Effect 的實戰分享 2:簡易爬蟲
系列文
Effect 魔法:打造堅不可摧的應用程式14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言