Magnolia Tech

いつもコードのことばかり考えている人のために。

『Scala関数型デザイン&プログラミング』の演習問題をScala3で解く その11

blog.magnolia.tech

第8章の「プロパティベースのテスト」に入ります。

この章は一度作ったAPI設計を、その後の検討で作り直す流れになっていて、演習問題の答えが都度変化していきます。そのため、正解を参照しようとしても用意されている正解は先回りして後半の仕様まで取り込まれているものが用意されていて本書の記載に沿って順番に解いていく時点では単なるコピペができません。一方でここで実装したライブラリは後半のモノイドの章でも利用されるので、動くところまで持っていく必要があります。

もともと、プロパティベースのテストに慣れていないと、新しい概念と実装の両方をこなしていかないといけないので、混乱しがちです。ここはやはり、元となったScalaCheckの使い方を十分に理解して進めていくと良いでしょう。

プロパティベースのテスト

ScalaCheckについて

第8章はプロパティベースのテストを扱いますが、本書でもそのベースになっているのはScalaCheckというライブラリです。

scalacheck.org

本書にも、プロパティベースのテストに関する概念を習得するために必要なことが十分に書かれていますが、やはり元となったライブラリの理解がある程度進んでおいた方が、演習問題を解きやすくなるでしょう。

scalacheck/UserGuide.md at main · typelevel/scalacheck · GitHub

特にEXERCISE 8.1と、8.2に相当コードをまずはScalaCheckで実装してみましょう。

AirSpecとの連携

ScalaCheckは単体でも使えますが、他のテスティングフレームワークとの組み合わせによる使い方が一般的です。ScalaのメジャーなテスティングフレームワークはどれもScalaCheckとの連携が用意されていますが、初回からお進めしているAirSpecにも連携機能が有ります。

https://wvlet.org/airframe/docs/airspec#property-based-testing-with-scalacheck

PropertyCheckというtraitをインポートするとAirSpecが用意するforAll関数が使えるようになります。

import wvlet.airspec.*
import wvlet.airspec.spi.PropertyCheck

class PropertyBasedTest extends AirSpec with PropertyCheck:
  test("testAllInt") {
    forAll{ (i:Int) => i.isValidInt shouldBe true }
  }

ただし、このforAllメソッドはScalaCheckが用意したものとは異なり、ScalaCheckのforAllの戻り値の型はPropですが、AirSpecのforAllUnitを返します。つまり、複数のforAllを実行し、すべてのテストがパスすることを確認する際に、Propが提供する&&メソッドは使えません。

ただし、複数のforAllを実行して結果を得るだけであれば、単純にforAllを複数並べておけば大丈夫です。どれが一つでも失敗すれば、テスト全体が失敗します。

forAll(Gen.listOfN(0, Gen.choose(0, 100))) { l => l.sum shouldBe 0 }
forAll(Gen.listOfN(1, Gen.choose(0, 100))) { i => i.sum shouldBe i(0) }

本書の演習問題ではforAllPropを返すインタフェースになっています。少し混乱しがちですが、演習問題を解く際には気をつけていきましょう。

scalapropsについて

Scala用のプロパティベースのテスティングフレームワークとしてscalapropsというのも有ります。

github.com

開発の経緯や、ScalaCheckとの違いなどは、この発表資料にまとまっています。

xuwei-k.github.io