Часть 1. Введение. ScalaCheck — это комбинáторная библиотека, значительно облегчающая написание модульных тестов на Scala. В ней используется подход property-based тестирования, впервые реализованный в библиотеке QuickCheck для языка Haskell. Существует множество реализаций QuickCheck: как для Java, C, а так же других языков и платформ. Использование данного подхода позволяет значительно сократить время на разработку тестов. Эта серия статей во многом похожа на мою предыдущую, посвященную Parboiled, поэтому и структура повествования будет похожей. Я расскажу вам, для чего всё это нужно, затем мы научимся смотреть на мир сквозь призму свойств и генераторов, а потом перейдём к более сложным вещам. Заинтересовало? Прошу под кат. Структура цикла Модульное тестирование является одним из важнейших подходов в разработке программного обеспечения. Даже если ваша программа при компиляции проходит проверки на уровне системы типов, это еще не означает, что в ней отсутствуют логические ошибки. Это значит, что каким бы мощным ни был ваш язык программирования, без тестирования кода не обойтись. Однако, стоимость тестирования весьма высока: помимо потраченных человеко-часов требуется тратить нечеловеко-yсилия на рутинное написание модульных тестов. Из-за этого многие заказчики экономят на тестировании, чем многие программисты пользуются с превеликой радостью: модульные тесты писать скучно (но нужно!).  Cогласитесь, покрывать огромное количество случаев и писать однотипные синтаксические конструкции изо дня в день — удовольствие сомнительное. К сожалению, даже типобезопасный код на функциональных языках нужно тестировать. И хотя уже само применение функциональной парадигмы значительно облегчает модульное тестирование — отсутствие побочных эффектов позволяет рассматривать меньше краевых случаев и совсем забыть о тестировании состояния, — количество тестирующего кода это значительно не сокращает. Кроме того, несмотря на то, что ручное тестирование даст вам некоторую уверенность в вашем коде, оно не может гарантировать, что со временем не обнаружится какой-нибудь частный случай, который вы упустили из виду. Подход ScalaCheck позволяет перейти на следующий уровень абстракции и избежать значительной части этой рутины, одновременно повысив читаемость тестирующего кода и сократив его объем (что положительно скажется на его ремонтопригодности). Покрытие вашего кода тестами также заметно возрастет. И чтобы добиться этого, нужно только познакомиться с несколькими новыми концепциями. Прежде всего, давайте разберемся в том, что является свойством. Если в двух словах, то свойство — это некоторое логическое высказывание, связывающее входные значения тестируемой функции и полученные на них результаты. При этом само слово «свойство» здесь следует понимать не в бытовом программистском смысле (принадлежащие некоторому объекту данные), а в математическом — как некоторый закон или правило, справедливые целиком для некоторого множества объектов. Вспомните, например, свойства ассоциативности или дистрибутивности в алгебре вещественных чисел. При тестировании свойств нас не волнуют детали реализации тестируемой функции,
а только лишь ее входные и выходные данные, спецификация. Возьмем теперь для примера следующее простое свойство, записанное на языке Лейбница: ScalaCheck позволяет записать его практически в оригинальной математической форме: По-русски это читается примерно так: «Для любого вещественного числа x, не равного нулю, x² всегда больше нуля». Как видите, свойство представляет собой более высокий уровень абстракции, чем традиционные assertion-тесты в JUnit. Однако, в конечном итоге все сводится к ним: на основе абстрактного описания свойств ScalaCheck генерирует вполне конкретные тесты отдельных значений, сопоставимые по качеству с теми, которые бы вы писали руками. В JUnit4 появился интересный механизм под общим названием theories. (Ох, они бы ещё гипотезами их назвали…) Теории работают весьма похожим на ScalaCheck образом, вот только не умеют генерировать случайные входные данные и выполнять минимизацию (shrinking). Итак, что же такое теория в представлении JUnit? Прежде всего, это специальный тип модульного теста. Теория проверяет, действительно ли некоторое условие истинно для каждого элемента из заданного множества тестовых данных (они называются data points). Это позволяет один раз запрограммировать логику проверки, а затем быстро прогнать её на различных наборах данных. Для того чтобы ваш метод стал теорией, его нужно соответствующим образом проаннотировать: добавить @Theory. Входные данные аннотируются как @DataPoint. Этого достаточно, чтобы раннер догадался запустить тест несколько раз: по одному разу для каждого дата-поинта. Вот небольшой пример, нагло позаимствованный из документации JUnit: Этот механизм похож на тот, что использует ScalaCheck. Есть лишь два маленьких отличия: Именно эти отличия позволяет ScalaCheck одержать победу над теориями как по объему кода, так и по количеству тестов. Но если вам всё же интересно узнать больше о теориях, вы можете
почитать про них здесь. Вопреки возможным подозрениям читателя, ScalaCheck создавался не для того, чтобы полностью вытеснить ScalaTest или пресловутый JUnit, а для того, чтобы внести в процесс модульного тестирования дополнительные преимущества, как то: Тем не менее, свойство-ориентированное тестирование — не серебряная пуля.
У этого подхода есть и недостатки: ScalaCheck можно использовать так же, как и любой другой фреймворк для тестирования. Вам придется думать несколько иначе, но, в конечном итоге, ScalaCheck позволит вам написать практически любой тест — правда, не всегда самым эффективным, удобным и читаемым способом. Однако, есть области где ScalaCheck по-настоящему хорош: ScalaCheck — это: После того, как мы определились с тем, нужно ли нам свойство-ориентированное тестирование и ScalaCheck в частности, давайте приступим к подготовительным работам. Добавьте следующую зависимость в ваш проект (я рассчитываю что вы, уважаемый читатель, уже перешли на Scala 2.12): Так же предполагается, что вы используете последнюю версию. Существует проблема при использовании устаревших версий библиотеки в связке с Scala 2.12. Будьте осторожны. Как уже было сказано ранее, ScalaCheck построен на двух основных концепциях: на свойствах и генераторах. Свойства хорошо освещены во множестве блогов, в том числе и русскоязычных. Поэтому больше внимания я постараюсь уделить генераторам. В этой части мы бегло ознакомимся как со свойствами, так и с генераторами. Свойство представляет собой минимальный тестируемый модуль. Представлено экземпляром класса org.scalacheck.Prop. Простейший пример: На практике генераторы приходится писать не реже, чем свойства. Для того чтобы воспользоваться ими, вам следует проимпортировать org.scalacheck.Gen. Также в ScalaCheck имеется набор готовых генераторов для стандартных типов: Вы также можете объединять имеющиеся генераторы, применяя к ним map и for comprehension: И свойства и генераторы мы рассмотрим подробнее в следующих статьях. Следующая статья серии будет посвящена генераторам. Спасибо что дочитали, оставайтесь на связи. Lead software engineer