1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Jest + Property based Testing

Last updated at Posted at 2021-06-08

はじめに

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

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

環境

準備

作業ディレクトリを用意

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

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

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

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

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

編集前

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" を以下のように書き換える

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

実践

題材

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

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

テスト対象

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

テスト内容

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

共通部分

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

$ mkdir src
$ touch src/app.js
src/app.js
const succ = (x) => {
  return x + 1;
};

module.exports = {
  succ,
};

jest のみ使用

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

$ mkdir test
$ touch test/app-jest.test.js
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);
  }
);

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

$ npm test

実行結果
実行結果

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

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

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

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

jest + fast-check を使用

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

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

$ touch test/app-fast-check.test.js
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の各関数の詳細については、公式およびGithubを参照してください

上記コードでは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を参照してください

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

$ touch test/app-jest-fast-check.test.js
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 を試してみよう

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?