TL;DR
- 単体テストと型チェックは補い合う関係にある。
- できるだけ型チェックに任せ、型でチェックできない振る舞いには単体テストを書く。
内容
単体テストと型チェックの役割
単体テストは、特定の入力に対して動作が正しいことを示す。つまり、正しさの下限を保証する。一方、型チェックは、誤った型を返すといった、ある種の不正確な動作をすべて排除する。つまり、不正確さの上限を保証する。
できるだけ型チェックに任せる
品質保証の主な目標の 1 つは、問題をできるだけ早く、修正のコストが低いうちに見つけることである。型チェックは単体テストや統合テストよりも早く結果が出るので、最も理想的である。そのため、できるだけ型チェックに任せたほうがよい。
型でチェックできない振る舞いはテストする
ただし、型でチェックできない振る舞いには単体テストを書く必要がある。たとえば、ユーザを更新する次の関数を考える。
interface User {
id: string
name: string
memberSince: string
}
declare function updateUserById(
id: string,
update: Partial<Omit<User, "id">> & { id?: never },
): Promise<User>この型は ID を変更できないことを表そうとしているが、実行時には強制できない。JavaScript からなら id を渡して呼べてしまう。そこで、関数が例外を投げるようにして、単体テストを書いたほうがよい。
test("invalid update", () => {
// @ts-expect-error updateUserById で ID は更新できない
expect(() => updateUserById("123", { id: "234" })).toReject()
})また、型がロジックを持つ場合は、型そのもののテストが必要になる(項目55)。型のテストは単体テストとは別物である。