Specs2のDSLでテストケースを書いていて、値クラス周りで少しつまづいたので、その時に発生したエラーとエラーの原因、その解決策を紹介します。
この記事中ではSpecs2と値クラスについての詳細な解説は行わないので、詳細な解説はドキュメントなどを読んでください。
環境
- Scala 2.12.7
- specs2-core 4.3.4
本文
エラーが発生する状況
Specs2は、以下のようなshould
とin
のようなDSLを使ってテストケースを記述することができます。
"#context" should {
"testCase1" in {
...
}
"testCase2" in {
...
}
}
そして、値クラスは、以下のようにAnyVal
を継承することで作成することができます。
case class ValueClass(value: String) extends AnyVal
ここで、Specs2のテストケース内部で、入力値として値クラスを何度も繰り返し使いたいということがありました。
テストケース毎に入力値は変化するので、以下のように値クラスの値をval
変数として保持します。
"#context" should {
"testCase1" in {
val input = ValueClass("foo")
...
}
"testCase2" in {
val input = ValueClass("bar")
...
}
}
しかし、この状態でコンパイルを行うと、以下のようなエラーが発生してしまいます。
[error] Result type in structural refinement may not refer to a user-defined value class
「構造的部分型はユーザー定義の値クラスを参照しないかもしれない」といった意味のエラーだと思われます。では、なぜこのようなエラーが発生するのでしょうか?
原因
Specs2のDSLにおけるin
で記述するテストケース内部の「値クラスのval
変数への代入」が、「テストケースを表現する構造的部分型の公開メンバ(すなわち公開メソッド)」になっているので、「構造的部分型はメソッドのパラメータや戻り型に値クラスを取ることができない」というルールに違反していたことが原因として挙げられます。
ポイント
- Specs2のDSLにおける
in
で記述するテストケースの内部は、構造的部分型になっている - 構造的部分型はメソッドのパラメータや戻り型に値クラスを取ることができない
値クラスが構造的部分型のメソッドのパラメータや戻り型として扱うことができないことについては公式ドキュメントに記述があります。
回避策
原因はin
で記述するテストケース内部で値クラスを公開メソッドの返り値にしていることです。
であれば、これを非公開にしてあげれば、これはメソッドではなくなりただの非公開メンバになります。
つまり「構造的部分型はメソッドのパラメータや戻り型に値クラスを取ることができない」のルールを回避できるわけですね。
最終的には以下のようなコードになります。
"#context" should {
"testCase1" in {
private val input = ValueClass("foo")
...
}
"testCase2" in {
private val input = ValueClass("bar")
...
}
}