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

パート3.プロパティ


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


サイクル構造



プロパティの組み合わせ


定数プロパティ


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



メソッドProp.passedおよびProp.falsifiedについてはすでによく知っています: Prop.passedは、 forAllコンビforAllテストプロパティの正常な合格に対応し、 forAllは、 forAllコンビforAllを使用したプロパティの少なくとも1つのテストの不合格に対応します。 それらに加えて:



プロパティの組み合わせ


ScalaCheckでは、 forAllthrows 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.classifyProp.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つである縮小について説明ます。 あなたが興味を持っていたことを願っています。 次の記事はすぐになります。



Source: https://habr.com/ru/post/J323038/


All Articles