🔥

Property based testingを入門してみた

2022/05/06に公開約3,100字

はじめに

学習コミュニティでProperty based testingの話題になったのですが、全く知らなかったため実際に試してみました。そして学んだ事を要約させて頂きます。

実装したコードはこちらになります。

https://github.com/knj-labo/learn_property-based-testing

Property based testing とは?

テストデータをランダムに半自動生成する。その生成された全ての値について、満たすべき性質を満たしているかテストすること。
ライブラリを使用して、実装していくことになりそうですね。今回は fast-check を利用していきます。

参考

https://medium.com/criteo-engineering/introduction-to-property-based-testing-f5236229d237

主な代表的なライブラリ

参考

https://jessitron.com/2013/04/25/property-based-testing-what-is-it/

なにのために Property based testing があるのでしょうか?

単体テストを実装する場合、境界値を懸念し、テストで保証すべき処理を開発者が事前に考えておく必要で、さらに複雑な機能をテスト実装する場合、テストケースを網羅するのは難しいですね。

そこで役に立つのが、Propert based testingになります

Property based testingは境界値テストとして有効で、十分なテストカバレッジを保ちながら、テストケースを数を管理可能な程度に減らすことをしてくれます。

実践してみる

では早速、はじめてみます。

実装はこちらを参考にしています。

fc.property()の第一引数にfc.string()で入力最低文字数と入力最高文字数を指定してランダムなデータを生成し、第二引数で受け取ったデータでテストを実行していますね。

example
import fc from 'fast-check';

...

describe('パスワードは必ず8文字以上で20文字以下でならなければならない場合で', () => {
    it('パスワードの値が8文字未満の場合は、falseを返す', () => {
        fc.assert(
            fc.property(fc.string({minLength: 0, maxLength: 7}),(password: string) => {
                expect(validatePassword(password)).toBe(false)
            })
        )
    })
    it('パスワードの値が21文字以上の場合は、falseを返す', () => {
        fc.assert(
            fc.property(fc.string({minLength: 21, maxLength: 100}),(password:string) => {
                expect(validatePassword(password)).toBe(false)
            })
        )
    })
})

出力されるランダムなデータはこちらになります。

example
...
 fc.assert(
     fc.property(fc.string({minLength: 0, maxLength: 7}),(password: string) => {
        console.log(password); 
     })
)
...
// 出力結果
XC0jf                                                                                                                             
z@i0]1*
zPq
Me3al
z V,
z$w`[                                                                                                                               
W"z%_z

さらにランダムに生成される値をfilter()map()を使用してデータ加工し、望んだデータ生成することも可能になります。

example
const char = (charCodeFrom:number,charCodeTo: number) => fc.integer().map(String.fromCharCode);

const filteredStrings = (min: number,max: number,filter:RegExp) => {
    return fc.array(
        char(33, //'!'
        126 //'~'
    ).filter(c => filter.test(c)),{minLength: min, maxLength: max}).map(arr => arr.join(''));
}

const filteredNumber = (min:number,max:number) => filteredStrings(8,20,/^[0-9]$/);

しかも、fc.assert()ごとに100回ほどランダムに生成されたデータに対してテストを実行されているため網羅性は十分になります。

参考

https://github.com/dubzzz/fast-check/blob/main/documentation/HowItWorks.md#runner

さいごに

テストケースの増加していくごとに、今まで行なっていた自動テストが難しくなっていたのを実感していため、このような手法には驚かされました。実務でも使用できる場合は積極的に実装していきたいですね。

GitHubで編集を提案

Discussion

ログインするとコメントできます