ScalaCheckについて。 物性 パート3

パート3.プロパティ







前のパートでは、プロパティに精通し、ジェネレーターと組み合わせてテストすることができました。 このチュートリアルでは、プロパティをさらに詳しく見ていきます。 この記事は2つの部分で構成されています。最初の部分は技術的なものであり、プロパティコンビネーターとScalaCheckライブラリの他の機能について説明します。 このパートでは、さまざまなテスト手法に焦点を当てます。







サイクル構造









プロパティの組み合わせ



定数プロパティ



Scalacheckには永続プロパティがあり、常に同じ結果を返すプロパティです。 そのようなプロパティの例は次のとおりです。









メソッドProp.passed



およびProp.falsified



についてはすでによく知っています: Prop.passed



は、 forAll



コンビforAll



テストプロパティの正常な合格に対応し、 forAll



は、 forAll



コンビforAll



を使用したプロパティの少なくとも1つのテストの不合格に対応します。 それらに加えて:









プロパティの組み合わせ



ScalaCheckでは、 forAll



throws



exists



プロパティを任意にネストできます。 forAll



例を使用してこれを示しforAll









 import org.scalacheck.Prop.forAll //     . val intsum = forAll { x: Int => forAll { y: Int => (x + y).isInstanceOf[Int] } }
      
      





支柱スロー



式の実行中に予期される例外がスローされた場合にのみtrueを返す論理メソッド。 次のようにプロパティを使用できます。







 import org.scalacheck.Prop //  : val p0 = Prop.throws(classOf[ArithmeticException])(3 / 0) p0.check // + OK, proved property.
      
      





ただし、定数をテストすることはほとんど意味がありません。 0で任意の整数を除算するときにProp.throwsを確認します。







 val p = Prop.forAll { x: Int => Prop.throws(classOf[ArithmeticException]) (x / 0) } p.check // + OK, passed 100 tests.
      
      





Prop.forAll



論理で汎用量指定子と呼ばれ、最もよく使用するプロパティでもあります。 forAll



渡される条件は、 Boolean



またはProp



クラスのインスタンスである必要があります。







特定のプロパティをテストするとき、ライブラリは

すべての有効な値の有効性を確認できます。 したがって、しばしば

これは、設定で説明されているいくつかの数値によって満たされます。

デフォルトでは、この数は100です。手動で変更できます。

プロパティを設定します。 構成の詳細については、以下で学習します

シリーズの次の記事。


Prop.exists



それは存在の数量詞のように正確にふるまいます。 このコンビforAll



の場合、入力データのセットの少なくとも1つの要素が所定の条件を満たす場合にプロパティがカウントされることを除いて、動作はforAll



に非常に似ています。 実際には、 Prop.exist



使用には問題があります。特定の条件を満たすケースを見つけるのは非常に難しいからです。







 import org.scalacheck.Prop val p1 = Prop.exists { x: Int => (x % 2 == 0) && (x > 0) }
      
      





p1.check



呼び出されるとp1.check



ScalaCheckは次を表示します。







 scala> p1.check + OK, proved property. > ARG_0: 73115928
      
      





ScalaCheckに不可能を求めてみてください:







 val p2 = Prop.exists(posNum[Int]) { x: Int => (x % 2 == 0) && (x < 0) }
      
      





ScalaCheckは、私たちに合った最初の要素を見つけるとすぐに、プロパティがテスト済み (合格)ではなく、 証明済みであることを報告します。







 scala> p2.check ! Gave up after only 0 passed tests. 501 tests were discarded.
      
      





Prop.exists



間違いなく誰かの問題を解決できます。 私の練習では、このプロパティは使用されていません。







プロパティの命名



ネーミングは、ジェネレーターとプロパティの両方にとって良い習慣です。 プロパティに名前を付ける場合、ジェネレーターと同じ演算子が使用されます。文字列または文字をプロパティ名として使用できます。 使用される演算子:|



および|:









 //  |: ,     . 'linked |: isLinkedProp //  :| ,     . isComplete :| "is complete property
      
      





論理演算子



プロパティは論理式です。 ScalaCheckでは、プロパティに関して論理演算子を使用できます。 &&



および||



宣言されたProp



ステートメント その動作は、 Boolean



クラスの同じ名前の演算子と完全に一致します。 上記の演算子に加えて、シンボリック名の同義語Prop.all



およびProp.atLeastOne



ます。







論理演算子を使用すると、単純なプロパティから複雑なプロパティを収集できます。 さらに、1つの式でProp



インスタンスとブール変数を組み合わせることもできます。このため、Scalaコンパイラが型キャストを自動的に実行できない場合の1つであるため、 Prop.propBoolean



に明示的に追加する必要があります。 変換を明示的に実行する場合は、次を実行できます。







 // January has April showers and... val prop = Prop.propBoolean(2 + 2 == 5)
      
      





それでは、リストとreversed



メソッドの例を見てみましょう:







 //    ,   reversed. def elementsAreReversed(list: List[Int], reversed: List[Int]): Boolean = //    ,    ... if (list.isEmpty) true else { val lastIdx = list.size - 1 // ...        //   . list.zipWithIndex.forall { case (element, index) => element == reversed(lastIdx - index) } }
      
      





この方法は、 reversed



方法の主な特性を完全に説明しており、非常に十分です。 ただし、現在のタスクはプロパティの明確なステートメントではなく、ScalaCheckの機能のデモです。 したがって、 elementsAreReversed



暗黙的に表現される耳によって、さらに2つのプロパティを描画しelementsAreReversed









 val hasSameSize = reversed.size == list.size val hasAllElements = list.forall(reversed.contains)
      
      





これらのプロパティはブール値です。 ラベルを追加すると( propBoolean



スコープ内にある場合)、変数がProp



自動的に変換されます。 では、最初の複合プロパティ説明し、同時にラベルを使用してみましょう。







 val propReversed = forAll { list: List[Int] => val reversed = list.reverse if (list.isEmpty) //  ,   Prop.propBoolean   (list == reversed) :| "     " else { val hasSameSize = reversed.size == list.size val hasAllElements = list.forall(reversed.contains) hasSameSize :| "  " && hasAllElements :| "    " && ("    " |: elementsAreReversed(list, reversed)) } }
      
      





彼らが欲しいものを受け取っていないとき



エラーが発生した場合、どの値があり、どの値を期待しているかを確認しますか? ScalaCheckはこの機会を提供します:単純な等式==



を演算子?=



または=?



置き換えるだけ=?



。 これを行うと、ScalaCheckはこのプロパティが実行されるときに式の両方の部分を記憶し、プロパティが間違っていることが判明すると、両方の値が表示されます。







 ! Falsified after 0 passed tests. > Labels of failing property: Expected 4 but got 5 > ARG_0: "
      
      





演算子を使用するには?=



And =?



、スコープ内にProp.AnyOperators



を追加する必要があります。







 import org.scalacheck.Prop.{AnyOperators, forAll} val propConcat = forAll { s: String => 2 + 2 =? 5 }
      
      





記号の近くにある値は重要?



等号に最も近い値であると予想されます。







また、ScalaTestと統合し、それに接続されているゲーマーを使用して、読み取り可能なエラーメッセージを受信することもできます。 これについては、「統合と設定」セクションで詳しく説明します。







統計を収集します



分類する



すべてのテストが成功し、すべてが成功したとしても、テストで使用された情報を取得したい場合があります。 たとえば、メソッドの非自明な前提条件があり、ScalaCheckが入力データを選択する難しさを正確に知りたい場合。 したがって、統計が必要な場合Prop.classify



Prop.classify



サービスにあります。







 import org.scalacheck.Prop.{forAll, classify} val classifiedProperty = forAll { n: Double => // classify     , //     . classify(n < 0, "negative", "positive") { classify(n % 2 == 0, "even", "odd") { n == n } } }
      
      





必要な数の分類子を追加できます。ScalaCheckはそれらを一緒にマージし、分布として提示します。







 + OK, passed 100 tests. > Collected test data: 33% odd, negative 31% even, negative 18% odd, positive 18% even, positive
      
      





集める



classify



加えて、収集および統計のためのより一般化された方法がありますProp.collect



メソッドは、関心のある統計を収集し、最も便利な名前でグループ化します。







 collect(label)(boolean || prop)
      
      





ちなみに、名前は任意のタイプにすることができますtoString



は自動的に呼び出されます。 最も単純な例を考えてみましょう。







 val moreLessAndZero = Prop.forAll { n: Int => val label = { if (n == 0) 0 //   ,   toString. else if (n > 0) "> 0" else "< 0" } collect(label)(true) //    . }
      
      





check



メソッドを呼び出した後、次のことができます。







 + OK, passed 100 tests. > Collected test data: 46% < 0 45% > 0 9% 0
      
      





リファレンス実装



したがって、リストの別の実装を書いていると想像してください。 あなたの実装は他のものよりも間違いなく優れています(少なくとも作者はこれを頼りにしています)。 次に、リストをテストします。







の実現につながる多くの理由があります

すでに標準ライブラリにあるもの。これは必ずしも知識の渇望ではない

または自己開発。

たとえば、すでにJDKに実装されているConcurrentHashMap



クラスを使用して、何らかの方法でリストをテストすることは素晴らしいことです。 そして、これを行うことができます。厳密な条件と契約のセットを含む仕様を作成する代わりに、既知の作業実装( reference )を使用して暗黙的に仕様を指定できます。 このアプローチは、テストで広く使用されています。 英語のソースでは、 参照実装と呼ばます。







 import org.scalacheck.Prop.AnyOperators import org.scalacheck.Properties // ,        //     . def listsGen: Gen[(List, MyList)] = ??? object MyListSpec extends Propertes("My Awesome List") { property("size") = Prop.forAll(listsGen) { case (list, myList) => list.size =? myList.size } property("is empty") = Prop.forAll(listsGen) { case (list, myList) => list.isEmpty =? myList.isEmpty } }
      
      





対称プロパティ



ラウンドトリッププロパティとも呼ばれます 。 これを思い付かない方が簡単です。特定の可逆関数を取得して2回適用し、可逆性をテストします。







 def neg(a: Long) = -a val negatedNegation = forAll { n: Long => neg(neg(n)) == n }
      
      





このプロパティは、 neg



メソッドを完全には説明せず、その機能についても説明しません。 しかし、それはその可逆性について語っています。







おそらく、この例は、悪名高いList.reverse



ように、多数のチュートリアルで見つけることができますが、原始的なものに思えます。 ただし、このアプローチを適用できるより複雑なシステムがあります。すべての種類とストライプのパーサーとエンコーダーです。 たとえば、対称プロパティを使用してパーサーをテストする場合、アクセスできない場所でエラーが見つかる場合があります。







以下のメソッドはテキストを解析し、それに基づいて抽象構文ツリー(AST)を作成しますprettyPrint



メソッドはこのツリーをテキストに変換します。







 // , -    , // AST    . sealed trait AST = ... //    . def parse(s: String): AST = ... // ,   . val astGen: Gen[AST] = ... // ,  AST  .   //      : // ScalaCheck    . def pretty(ast: AST): String = ... //  ,    ,  //   . val prop = forAll(astGen) { ast => parse(pretty(ast)) == ast }
      
      





結論として



ほとんどのScalaCheckチュートリアルでは、すぐにプロパティとその先入観のある分類について紹介し、その後、すでに記述されたコードからプロパティを分離することの難しさについて説明します。 これは部分的に真実です。適切なトレーニングがなければ、テストできるプロパティを特定するのは簡単ではありません。







ただし、ScalaCheckを使用した謙虚な経験で最も難しいのは、ジェネレーターをコンパイルすることです。 このプロセスには、プロパティを記述するよりも多くの労力が必要です。単一のプロパティを記述するには、数十または2つのジェネレータを記述する必要があります。 だからこそ、多くの人にとって奇妙に思えるかもしれませんが、私はジェネレーターでストーリーを始めました。







次のセクションでは、プロパティ指向テストの長所の1つである縮小について説明ます。 あなたが興味を持っていたことを願っています。 次の記事はすぐになります。








All Articles