Jest + Property based Testing 

# はじめに

今読み進めている[「関数型プログラミングの基礎 JavaScriptを使って学ぶ」][link6]の中で紹介されていた Property based testing に興味がわいたのでJavaScript環境で試した話です。

なお、Property based testing とはなんぞや という話については、本稿では詳しく触れません。そちらを知りたい方は、諸先輩方が投稿している[Qiita記事][link3]などで調べて見てください。

## 環境

- Node.js : v14.17.0

  - [https://nodejs.org/ja/][link1]

- npm : v6.14.13

- jest : v27.0.1
  - [https://jestjs.io/ja/][link2]
  - Facebook のオープンソース
  - フルスタックテスティングフレームワーク
  
- fast-check : v2.14.0
  - [https://dubzzz.github.io/fast-check.github.com/][link4]
  - Property based testing のフレームワーク

- jest-fast-check : v1.0.2
  - [https://github.com/dubzzz/jest-fast-check][link5]
  - jest と fast-check の統合用ライブラリ（記述を簡素化できるだけで必須ではない）

# 準備

## 作業ディレクトリを用意

（Node.js はインストール済みの前提です。）

```shell
$ mkdir property-based-testing
$ cd property-based-testing
$ npm init --yes
```

## ライブラリをインストール

```shell
$ npm install --save-dev jest fast-check jest-fast-check
```

## テスティングフレームワークの使用準備

編集前

```json:package.json
{
  "name": "property-based-testing",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "fast-check": "^2.15.0",
    "jest": "^27.0.3",
    "jest-fast-check": "^1.0.2"
  }
}
```

`"scripts"` の `"test"` を以下のように書き換える

```json:package.json抜粋
  "scripts": {
    "test": "jest"
  },
```

# 実践

## 題材

[「関数型プログラミングの基礎 JavaScriptを使って学ぶ」][link6] でも使われている [後者関数][link7] を使って以下の3つのケースに分けて各テスティングフレームワークを使って行きます。

- jest のみを使用
- jest + fast-check を使用
- jest + fast-check + jest-fast-check を使用

### テスト対象

```javascript
const succ = (x) => { return x + 1; };
```

### テスト内容

上記関数において `succ(0) + succ(x) = succ(succ(x))` という命題をProperty based testingを使って確認する

### 共通部分

3つのケースで共通となるテスト対象の関数を作成します。

```shell
$ mkdir src
$ touch src/app.js
```

```javascript:src/app.js
const succ = (x) => {
  return x + 1;
};

module.exports = {
  succ,
};
```

### jest のみ使用

テストコードを追加します

```shell
$ mkdir test
$ touch test/app-jest.test.js
```

```javascript:test/app-jest.test.js
const { succ } = require('../src/app');

const iterate = (init) => {
  return (step) => {
    return [init, (_) => {
      return iterate(step(init))(step);
    }];
  };
};

/* 無限の整数列を生成する */
const enumFrom = (n) => {
  return iterate(n)(succ);
};

/* ストリームのmap関数 */
const map = (transform) => {
  return (aStream) => {
    console.log(aStream);
    const head = aStream[0];
    return [transform(head), (_) => {
      return map(transform)(aStream[1]());
    }];
  };
};

/* ストリームの先頭から引数n分だけ取り出す */
const take = (n) => {
  return (aStream) => {
    if (n === 0) {
      return null;
    } else {
      return [aStream[0], (_) => {
        return take(n-1)(aStream[1]());
      }];
    }
  };
};

/* ストリームの全ての要素がtrueであるか判定する */
const all = (aStream) => {
  const allHelper = (aStream, accumulator) => {
    const head = aStream[0];
    const newAccumulator = accumulator && head;
    if (aStream[1]() === null) {
      return newAccumulator;
    } else {
      return allHelper(aStream[1](), newAccumulator);
    }
  };
  return allHelper(aStream, true);
};

/* 検証の対象となる命題 */
const proposition = (n) => {
  return succ(0) + succ(n) === succ(succ(n));
};

/* 100個の整数について命題が正しいか */
test(
  'should succ(0) + succ(x) = succ(succ(x))',
  () => {
    expect(
      all(
        take(100)(
          map(proposition)(enumFrom(0))
        )
      )
    ).toBe(true);
  }
);
```

テストの実行は以下のコマンドで行います

```shell
$ npm test
```

実行結果
![実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63427/27cb19bd-6934-b0f2-d4b8-f26ef4b76a50.png "実行結果")

上記のコードでは、1から100の100個の整数で命題が正しいかチェックしています

このようにProperty based testingはフレームワークを使用しないでも実現することができます

ただし、整数をランダムに生成したり、事前条件などを追加したりしようとすると柔軟性にかけます

つまり、検証用のテストデータ生成部分の柔軟性に難がある（手間がかかる）ということです

### jest + fast-check を使用

では、Property based testingのフレームワークを使用した場合にどうなるかみてみます

なお、fast-check の詳細は[公式][link4]を確認してみてください

```shell
$ touch test/app-fast-check.test.js
```

```javascript:test/app-fast-check.test.js
const { succ } = require('../src/app');
const fc = require('fast-check');

test(
  'should succ(0) + succ(x) = succ(succ(x))',
  () => fc.assert(
    fc.property(
      fc.integer({ min:1 }),
      (x) => {
        // console.log(x);
        expect(succ(0) + succ(x)).toBe(succ(succ(x)));
      }
    )
  )
);
```

fast-checkの各関数の詳細については、[公式][link4]および[Github][link8]を参照してください

上記コードでは`fc.integer()`が検証用のテストデータを生成している部分になります

コメントアウトしてる部分を有効にすると、1以上のランダムな整数がテストデータとして使われていることが確認できます

### jest + fast-check + jest-fast-check を使用

fast-check を使うことにより、テストデータの生成がとてもシンプルに記述することができました

ただ、fast-check のみ使用した場合だと、`fc.assert()`や`fc.property()`など、fast-check独自の記法が必要となり、jestとの記述の差異が生まれます

この差異を埋めるのがjest-fast-checkです

jest-fast-checkの詳細は[Github][link9]を参照してください

jest-fast-checkを使ったコードが以下になります

```shell
$ touch test/app-jest-fast-check.test.js
```

```javascript:test/app-jest-fast-check.test.js
const { succ } = require('../src/app');
const { testProp, fc } = require('jest-fast-check');

testProp(
  'should succ(0) + succ(x) = succ(succ(x))',
  fc.integer({ min:1 }),
  (x) => {
    expect(succ(0) + succ(x)).toBe(succ(succ(x)));
  }
);
```

jest-fast-checkのソースコードを読むとわかりますが、

`testProp()`が`fc.assert()`と`fc.property()`を`test()`内で実行するようなラッパーになっています

`testProp()`を使うと記述量を抑え、よりjestの記法に近い形でProperty based testingのコードを記述できます

## 参考記事

今回Property based testingを学習する際にこちらの記事を参考にさせていただきました

[Property based testing を試してみよう][link10]

[link1]:https://nodejs.org/ja/
[link2]:https://jestjs.io/ja/
[link3]:https://qiita.com/search?q=-tag+property-based-testing
[link4]:https://dubzzz.github.io/fast-check.github.com/
[link5]:https://github.com/dubzzz/jest-fast-check
[link6]:http://www.ric.co.jp/book/contents/book_1059.html
[link7]:https://ja.wikipedia.org/wiki/%E5%BE%8C%E8%80%85%E9%96%A2%E6%95%B0
[link8]:https://github.com/dubzzz/fast-check/tree/main/documentation
[link9]:https://github.com/dubzzz/jest-fast-check
[link10]:https://qiita.com/freddiefujiwara/items/e345f4a0610bf08deea7