👏

話題沸騰ポットに対して Property based testing を試してみる (1)蓋の開け閉め、水を充填させる

2020/10/26に公開

はじめに

Property based testing を試してみように引き続き
fast-checkを使ってProperty based testingを試しています
fast-checkは状態をランダムウォークさせて失敗するテストケースを見つける方法も実装されてるみたいですので後ほど状態に関しても試してみたいと思います

題材

後ほど状態遷移に関しては挑戦するとして、なにかいい題材はないかなと検索してところ
テスト設計コンテストU-30クラスでテストベースに指定されている
「話題沸騰ポット要求仕様書 (GOMA‐1015型) 第7版にすることにしました.
理由としては、後ほど試したい状態遷移のProperty based testingができそうなこと、
過去に色々な方がテスト設計を試みられているので、こちら参考にできそうだという理由からです。

準備

こちらに今回実装したコードを配置してみました。
Nodejsの環境が手元にあれば、試すことができます。

git clone https://github.com/freddiefujiwara/goma-1015.git
cd goma-1015
npm i
npm test

蓋の開け閉め

準備

Goma1015にopenとcloseという機能をつけます
いま蓋が開いているか、閉じているかを確認するために
isOpenという機能もついています
##Property based testing
Property based testingとしてはランダムに開け閉めをして
開いている、閉じているに関わらずopenすると蓋が開いて
closeすると蓋が閉じているかを確認します
fc.boolean()を使ってランダムにtrue/falseを発生させ
trueであれば、蓋を開け、falseであれば蓋を閉じます
実際の実装は下記になります

import fc from 'fast-check'
import Goma1015 from '../../src/lib/index'

describe('Goma1015', () => {
  it('can create new instance', () => {
    const g = new Goma1015()
    expect(g).not.toBeNull()
    expect(g).toBeInstanceOf(Goma1015)
  })
  it('can open or close', () => {
    const g = new Goma1015()
    expect(g.open).toBeDefined()
    expect(g.close).toBeDefined()
    expect(g.isOpen).toBeDefined()
    expect(g.isOpen()).toBe(false)
    fc.assert(
      fc.property(fc.boolean(), b => {
        if (b) {
          g.open()
          expect(g.isOpen()).toBe(true)
          return
        }
        g.close()
        expect(g.isOpen()).toBe(false)
      }),
    )
  })
})

水を充填させる

次に、ポットに水を充填させましょう
ボットに事前にfillと満タンになったかどうかを確認するfullという機能を追加しておきましょう
要求仕様書には書いてないですが下記とします

  • 蓋が開いていないと、水が入れられない (*this is not openの例外が発生)
  • マイナスの容量の水は入れられない (*this can't be filled with negative numberの例外が発生)
  • 1000ml以上は入れられない (*this is Fullの例外が発生)

これらをProperty based testingで確認していきましょう
fc.nat(1000)を使って1000mlまでの任意の容量の水を発生させ確認していきます

import fc from 'fast-check'

import Goma1015 from '../../src/lib/index'

describe('Goma1015', () => {
  .
  .
  .
  it('can fill', () => {
    let g = new Goma1015()
    expect(g.fill).toBeDefined()
    expect(() => g.fill(15)).toThrowError(/is not open/)
    g.open()
    expect(() => g.fill(-1)).toThrowError(/can't be filled with negative number/)
    expect(g.full).toBeDefined()
    expect(g.full()).toBe(false)
    g.fill(1000)
    expect(g.full()).toBe(true)
    expect(() => g.fill(1)).toThrowError(/is full/)
    g = new Goma1015()
    g.open()
    let water = 0
    fc.assert(
      fc.property(fc.nat(1000), w => {
        if (water + w > 1000) {
          expect(() => g.fill(w)).toThrowError(/is full/)
          return
        }
        g.fill(w)
        water += w
        expect(g.full()).toBe(1000 == water)
      }),
    )
  })
})

最後に

次は注いでみたいと思います
また、コードにもし間違いありましたらPull requestは大歓迎です。
長くなってしまいましたが 最後まで読んでいただきありがとうございました。

Discussion