這篇我們要來幫 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 時,可能有幾種不同的檢查方式
另外你可能還有聽過跟 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 怎麼幫助我們更好的寫測試