munit-scalacheck β€” это Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° ΠΈΠ½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΠΈ, которая ΠΎΠ±ΡŠΠ΅Π΄ΠΈΠ½ΡΠ΅Ρ‚ Π΄Π²Π° ΠΌΠΎΡ‰Π½Ρ‹Ρ… инструмСнта тСстирования Π² экосистСмС Scala:

  1. MUnit β€” соврСмСнный, свСрхбыстрый ΠΈ лСгковСсный Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊ для ΡŽΠ½ΠΈΡ‚-тСстирования, созданный ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ ΠΏΠΎΠ΄ Scala 3 (с Π½Π°Ρ‚ΠΈΠ²Π½ΠΎΠΉ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ ΠΊΠ°ΠΊ JVM, Ρ‚Π°ΠΊ ΠΈ Scala.js).
  2. ScalaCheck β€” Π·ΠΎΠ»ΠΎΡ‚ΠΎΠΉ стандарт для Property-Based Testing (тСстирования Π½Π° основС свойств) Π² Scala (Π²Π΄ΠΎΡ…Π½ΠΎΠ²Π»Π΅Π½Π½Ρ‹ΠΉ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΎΠΉ QuickCheck ΠΈΠ· Haskell).

Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° munit-scalacheck позволяСт ΠΏΠΈΡΠ°Ρ‚ΡŒ тСсты ScalaCheck прямо Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΏΡ€ΠΈΠ²Ρ‹Ρ‡Π½Ρ‹Ρ… тСст-классов MUnit, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ ΡƒΠ΄ΠΎΠ±Π½ΠΎΠ΅ ΠΊΠ»ΡŽΡ‡Π΅Π²ΠΎΠ΅ слово property(...) вмСсто стандартного test(...).


Π§Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ Property-Based Testing (ВСстированиС Π½Π° основС свойств)?

Π’ ΠΎΠ±Ρ‹Ρ‡Π½ΠΎΠΌ ΡŽΠ½ΠΈΡ‚-тСстировании ΠΌΡ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ тСстированиС Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°Ρ… (Example-Based Testing):

  • ΠœΡ‹ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ пишСм ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹Π΅ Π²Ρ…ΠΎΠ΄Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅: Β«Π”Π°Π½Π° Π½Π°Ρ‡Π°Π»ΡŒΠ½Π°Ρ позиция ΠΈ ΠΊΡƒΠ±ΠΈΠΊΠΈ [1, 2].Β»
  • ΠœΡ‹ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ пишСм ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΉ ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌΡ‹ΠΉ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚: Β«Π”ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ Ρ€ΠΎΠ²Π½ΠΎ 20 Π»Π΅Π³Π°Π»ΡŒΠ½Ρ‹Ρ… Ρ…ΠΎΠ΄ΠΎΠ².Β»

Π’ Property-Based Testing ΠΌΡ‹ Π½Π΅ ΠΏΡ€ΠΈΠ΄ΡƒΠΌΡ‹Π²Π°Π΅ΠΌ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ сами. ВмСсто этого ΠΌΡ‹ описываСм ΠΈΠ½Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹ (свойства) β€” ΠΏΡ€Π°Π²ΠΈΠ»Π°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΠΎΡΡ‚Π°Π²Π°Ρ‚ΡŒΡΡ истинными для Π»ΡŽΠ±Ρ‹Ρ… Π²Ρ…ΠΎΠ΄Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ….

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ свойства для Dice Chess:

«Для любой Π²Π°Π»ΠΈΠ΄Π½ΠΎΠΉ ΡˆΠ°Ρ…ΠΌΠ°Ρ‚Π½ΠΎΠΉ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ ΠΈ для любого случайного броска 3 ΠΊΡƒΠ±ΠΈΠΊΠΎΠ² , Ссли ΠΌΡ‹ ΠΎΡ‚Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΡƒΠ΅ΠΌ Ρ…ΠΎΠ΄Ρ‹, Ρ‚ΠΎ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π΅Π½Π½Ρ‹ΠΉ Ρ…ΠΎΠ΄ обязан Π»ΠΈΠ±ΠΎ вСсти ΠΊ Π²Π·ΡΡ‚ΠΈΡŽ короля, Π»ΠΈΠ±ΠΎ Π±Ρ‹Ρ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒΡŽ Ρ†Π΅ΠΏΠΎΡ‡ΠΊΠΈ Ρ…ΠΎΠ΄ΠΎΠ² максимальной Π΄Π»ΠΈΠ½Ρ‹.Β»

ScalaCheck Π±Π΅Ρ€Π΅Ρ‚ это свойство ΠΈ автоматичСски Π³Π΅Π½Π΅Ρ€ΠΈΡ€ΡƒΠ΅Ρ‚ 100 (ΠΈΠ»ΠΈ 1000) Π°Π±ΡΠΎΠ»ΡŽΡ‚Π½ΠΎ случайных ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΉ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΉ ΠΈ ΠΊΡƒΠ±ΠΈΠΊΠΎΠ², провСряя, Π½Π΅ сломаСтся Π»ΠΈ наш ΠΊΠΎΠ΄ Π½Π° ΠΊΠ°ΠΊΠΎΠΌ-Π½ΠΈΠ±ΡƒΠ΄ΡŒ Π±Π΅Π·ΡƒΠΌΠ½ΠΎΠΌ edge-case.


Для Ρ‡Π΅Π³ΠΎ munit-scalacheck Π½ΡƒΠΆΠ΅Π½ Π½Π°ΡˆΠ΅ΠΌΡƒ ΡˆΠ°Ρ…ΠΌΠ°Ρ‚Π½ΠΎΠΌΡƒ Π΄Π²ΠΈΠΆΠΊΡƒ?

Для Π΄Π²ΠΈΠΆΠΊΠ° Dice Chess Ρ‚Π°ΠΊΠΎΠ΅ тСстированиС β€” это ΡƒΠ»ΡŒΡ‚ΠΈΠΌΠ°Ρ‚ΠΈΠ²Π½ΠΎΠ΅ ΠΎΡ€ΡƒΠΆΠΈΠ΅ ΠΏΡ€ΠΎΡ‚ΠΈΠ² Π±Π°Π³ΠΎΠ². И Π²ΠΎΡ‚ ΠΏΠΎΡ‡Π΅ΠΌΡƒ:

1. Π‘ΠΎΡ€ΡŒΠ±Π° с ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ‚ΠΎΡ€Π½Ρ‹ΠΌ Π²Π·Ρ€Ρ‹Π²ΠΎΠΌ πŸ’₯

Π¨Π°Ρ…ΠΌΠ°Ρ‚Ρ‹ ΠΈΠΌΠ΅ΡŽΡ‚ колоссальноС пространство состояний ( ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΉ). Π”ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ случайных ΠΊΡƒΠ±ΠΈΠΊΠΎΠ² ΠΈ ΠΌΠΈΠΊΡ€ΠΎΡ…ΠΎΠ΄ΠΎΠ² ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅Ρ‚ число Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Ρ… сцСнариСв Π² Ρ€Π°Π·Ρ‹. ΠΠ°ΠΏΠΈΡΠ°Ρ‚ΡŒ Ρ€ΡƒΡ‡Π½Ρ‹Π΅ тСсты Π½Π° всС случаи Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ, ΡˆΠ°Ρ…ΠΎΠ², Ρ€ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ ΠΈ слоТных ΠΏΡ€Π΅Π²Ρ€Π°Ρ‰Π΅Π½ΠΈΠΉ пСшСк физичСски Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ. ScalaCheck Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ сцСнарии, ΠΎ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊ-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ Π΄Π°ΠΆΠ΅ Π½Π΅ задумаСтся (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€: Β«Π§Π΅Ρ€Π½Ρ‹ΠΉ слон Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½, Π±Π΅Π»Ρ‹ΠΉ ΠΊΠΎΡ€ΠΎΠ»ΡŒ ΠΏΠΎΠ΄ ΡˆΠ°Ρ…ΠΎΠΌ Π½Π° ΠΊΡ€Π°ΡŽ доски, Π° Π½Π° ΠΊΡƒΠ±ΠΈΠΊΠ°Ρ… Π²Ρ‹ΠΏΠ°Π»ΠΈ Ρ€ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° ΠΈ пСшка»).

2. АвтоматичСский поиск ΠΊΠΎΠ½Ρ‚Ρ€ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² (Fuzzing) πŸ”

Если Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌ Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΠΈ Ρ…ΠΎΠ΄ΠΎΠ² содСрТит Π»ΠΎΠ³ΠΈΡ‡Π΅ΡΠΊΡƒΡŽ ΠΎΡˆΠΈΠ±ΠΊΡƒ, ScalaCheck быстро сгСнСрируСт ΡΠ»ΡƒΡ‡Π°ΠΉΠ½ΡƒΡŽ ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ свойство Π½Π΅ выполнится, ΠΈ выдаст Π΅Ρ‘ Π½Π°ΠΌ.

3. Π£ΠΌΠ½ΠΎΠ΅ сТатиС ошибок (Shrinking) πŸ“‰

Π­Ρ‚ΠΎ ΠΎΠ΄Π½Π° ΠΈΠ· самых ΠΊΡ€ΡƒΡ‚Ρ‹Ρ… Ρ„ΠΈΡ‡ ScalaCheck. Если Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊ нашСл Π±Π°Π³ Π½Π° слоТнСйшСй ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ с 32 Ρ„ΠΈΠ³ΡƒΡ€Π°ΠΌΠΈ ΠΈ ΠΊΡƒΠ±ΠΈΠΊΠ°ΠΌΠΈ [1, 4, 6], ΠΎΠ½ Π½Π΅ просто Π²Ρ‹ΠΏΠ»ΡŽΠ½Π΅Ρ‚ эту ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ. Он Π½Π°Ρ‡Π½Π΅Ρ‚ автоматичСски Π΅Ρ‘ ΡƒΠΏΡ€ΠΎΡ‰Π°Ρ‚ΡŒ: ΡƒΠ±ΠΈΡ€Π°Ρ‚ΡŒ лишниС Ρ„ΠΈΠ³ΡƒΡ€Ρ‹ с доски, ΠΌΠ΅Π½ΡΡ‚ΡŒ ΠΊΡƒΠ±ΠΈΠΊΠΈ Π½Π° Π±ΠΎΠ»Π΅Π΅ простыС, ΠΏΠΎΠΊΠ° Π½Π΅ Π½Π°ΠΉΠ΄Π΅Ρ‚ минимально Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΡƒΡŽ ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ Π±Π°Π³ всё Π΅Ρ‰Π΅ воспроизводится. Π­Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚ ΠΎΡ‚Π»Π°Π΄ΠΊΡƒ нСвСроятно простой!


Как это выглядит Π² ΠΊΠΎΠ΄Π΅?

Π’ΠΎΡ‚ простой матСматичСский ΠΏΡ€ΠΈΠΌΠ΅Ρ€:

import munit.ScalaCheckSuite
import org.scalacheck.Prop._
 
class MathSpec extends ScalaCheckSuite {
 
  // ВмСсто test() пишСм property()
  property("слоТСниС ΠΊΠΎΠΌΠΌΡƒΡ‚Π°Ρ‚ΠΈΠ²Π½ΠΎ (a + b == b + a)") {
    // forAll автоматичСски сгСнСрируСт сотни случайных чисСл a ΠΈ b
    forAll { (a: Int, b: Int) =>
      a + b == b + a
    }
  }
}

А Π²ΠΎΡ‚ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΈΠ· нашСго созданного Ρ„Π°ΠΉΠ»Π° MutableLegalMovesFilterSpec.scala (Area D):

  // ΠœΡ‹ создали Π³Π΅Π½Π΅Ρ€Π°Ρ‚ΠΎΡ€ случайных бросков (3 числа ΠΎΡ‚ 1 Π΄ΠΎ 6)
  val diceGen: Gen[List[Int]] =
    Gen.listOfN(3, Gen.choose(1, 6))
 
  property("D3: ВсС ΠΎΡ‚Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΠΎΠ²Π°Π½Π½Ρ‹Π΅ Ρ…ΠΎΠ΄Ρ‹ ΡΠ²Π»ΡΡŽΡ‚ΡΡ подмноТСством ΠΏΡΠ΅Π²Π΄ΠΎΠ»Π΅Π³Π°Π»ΡŒΠ½Ρ‹Ρ…") {
    // forAll Π±Π΅Ρ€Π΅Ρ‚ ΡΠ»ΡƒΡ‡Π°ΠΉΠ½ΡƒΡŽ ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ ΠΈΠ· Π±Π°Π·Ρ‹ ΠΈ случайный бросок
    forAll(gameStateGen, diceGen) { (state, dice) =>
      val legal     = filterMoves(state, dice)
      val allPseudo = dice.distinct.flatMap(d => MoveGenerator.generateMoves(state, d))
 
      // Бвойство: ΠΎΡ‚Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ список Ρ…ΠΎΠ΄ΠΎΠ² Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ
      // Ρ…ΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π²ΠΎΠΎΠ±Ρ‰Π΅ Π½Π΅ гСнСрировался ΠΊΡƒΠ±ΠΈΠΊΠ°ΠΌΠΈ!
      legal.forall(allPseudo.contains)
    }
  }

РСзюмС:

munit-scalacheck β€” это инструмСнт, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΡ€Π΅Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ скучноС написаниС сотСн ΠΎΠ΄Π½ΠΎΡ‚ΠΈΠΏΠ½Ρ‹Ρ… ΡŽΠ½ΠΈΡ‚-тСстов Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ строгих матСматичСских свойств. Он Π³Π°Ρ€Π°Π½Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ Π²Ρ‹ΡΠΎΡ‡Π°ΠΉΡˆΡƒΡŽ Π½Π°Π΄Π΅ΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΈ ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½ΠΎΡΡ‚ΡŒ Π΄Π²ΠΈΠΆΠΊΠ° Dice Chess ΠΏΡ€ΠΈ Π»ΡŽΠ±Ρ‹Ρ… случайных ΠΈΠ³Ρ€ΠΎΠ²Ρ‹Ρ… ситуациях!