munit-scalacheck — это библиотека интеграции, которая объединяет два мощных инструмента тестирования в экосистеме Scala:
- MUnit — современный, сверхбыстрый и легковесный фреймворк для юнит-тестирования, созданный специально под Scala 3 (с нативной поддержкой как JVM, так и Scala.js).
- 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 при любых случайных игровых ситуациях!