Scala. Профессиональное программирование 2022. Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста
Скачать 6.24 Mb.
|
585 Листинг 25.7. Тестирование на основе свойств с помощью ScalaCheck import org.scalatest.wordspec.AnyWordSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import org.scalatest.matchers.must.Matchers.* import Element.elem class ElementSpec extends AnyWordSpec, ScalaCheckPropertyChecks: "elem result" must { "have passed width" in { forAll { (w: Int) => whenever (w > 0) { elem('x', w % 100, 3).width must equal (w % 100) } } } } AnyWordSpec — класс стиля, имеющийся в ScalaTest. Трейт Sca laCheckPro- pertyChecks предоставляет несколько методов forAll , позволяющих смеши вать тесты на основе проверки наличия свойств с традиционными тестами на основе утверждений или выявления соответствий. В данном примере проверяется наличие свойства, которым должен обладать фабричный метод elem . Свойства ScalaCheck выражены в виде значений функций, получающих в качестве параметров данные, необходимые для утверждений о наличии свойств. Эти данные будут генерироваться ScalaCheck. В свойстве, показан ном в листинге 25.7, данными является целое число w , которое представляет ширину. Внутри тела функции показан следующий код: whenever (w > 0) { elem('x', w % 100, 3).width must equal (w % 100) } Директива whenever указывает на то, что при каждом вычислении левосто роннего выражения в true правостороннее также должно быть true . Таким образом, в данном случае выражение в блоке должно быть true всякий раз, когда w больше нуля. А правостороннее будет выдавать true , если ширина, переданная фабричному методу elem , будет равна ширине объекта Element , возвращенного фабричным методом. При таком небольшом объеме кода ScalaCheck сгенерирует несколько пробных значений, проверяя каждое из них в поисках значения, для ко торого свойство не соблюдается. Если свойство соблюдается для каж дого значения, испытанного с помощью ScalaCheck, то тест будет прой ден. В противном случае он будет завершен с генерацией исключения 586 Глава 25 • Утверждения и тесты TestFailedException , которое содержит информацию, включающую зна чение, вызвавшее сбой. 25 .6 . Подготовка и проведение тестов Во всех средах, упомянутых в текущей главе, имеется механизм для под готовки и проведения тестов. В данном разделе будет дан краткий обзор того подхода, который используется в ScalaTest. Чтобы получить подробное описание любой из этих сред, нужно обратиться к их документации. Подготовка больших наборов тестов в ScalaTest проводится путем вложения Suite наборов в Suite наборы. При выполнении Suite набор запустит не только свои тесты, но и все тесты вложенных в него Suite наборов. Вложен ные Suite наборы, в свою очередь, выполнят тесты вложенных в них Suite наборов и т. д. Таким образом, большой набор тестов будет представлен деревом Suite объектов. При выполнении в этом дереве корневого Suite объекта будут выполнены все имеющиеся в нем Suite объекты. Наборы можно вкладывать вручную или автоматически. Чтобы вложить их вручную, вам нужно либо переопределить метод nestedSuites в своих Suite классах, либо передать предназначенные для вложения Suite объекты в кон структор класса Suites , который для этих целей предоставляет ScalaTest. Для автоматического вложения имена пакетов передаются имеющемуся в ScalaTest средству Runner, которое определит Suite объекты автоматиче ски, вложит их ниже корневого Suite и выполнит корневой Suite Имеющееся в ScalaTest приложение Runner можно вызвать из командной строки или через такие средства сборки, как sbt или maven . Самый распро страненный способ запуска ScalaTest, скорее всего, с помощью sbt 1 . Напри мер, для запуска тестового класса, показанного в листинге 25.6, с помощью sbt создайте новый каталог, поместите тестовый класс в файл с именем TVSetSpec.scala в подкаталог src/test/scala и добавьте следующий файл build.sbt в новый каталог: name := "ThankYouReader!" scalaVersion := "3.0.0" libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.9" % "test" 1 Установить sbt вы можете с сайта https://www.scalasbt.org/. Резюме 587 Затем вы можете войти в оболочку sbt , набрав sbt : $ sbt [info] welcome to sbt 1.5.2 (AdoptOpenJDK Java 1.8.0_262) sbt:ThankYouReader!> Вам будет выдан запрос на ввод названия проекта, в данном случае ThankYouReader! Если вы введете test , он скомпилирует и запустит тестовый класс. Результат показан на рис. 25.1. Рис. 25.1. Вывод, полученный при запуске org .scalatest .run Резюме В этой главе мы показали примеры примешивания утверждений непосред ственно в рабочий код, а также способы их внешней записи в тестах. Вы увидели, что программисты, работающие на Scala, могут воспользоваться преимуществами таких популярных средств тестирования от сообщества Java, как JUnit, и более новыми средствами, разработанными исключи тельно для Scala, например ScalaTest, ScalaCheck и specs2. И утверждения, встроенные в код, и внешние тесты могут помочь повысить качество ваших программных продуктов. Глоссарий Алгебраический тип данных (algebraic data type). Тип, определяемый путем предоставления нескольких альтернатив, у каждой из которых есть собственный конструктор. Обычно предоставляется возможность его декомпозиции через сопоставление с образцом. Эта концепция встре чается в языках специ фикаций и функциональных языках програм мирования. В Scala алгебраические типы данных можно эмулировать с помощью case классов. Альтернатива (alternative). Ветвь выражения match . Имеет вид « case пат- терн => выражение». Альтернативу также могут называть вариантом (case). Аннотация (annotation). Встречается в исходном коде и закрепляется за какойто частью синтаксиса. Аннотации обрабатываются компью тером, поэтому их фактически можно использовать для расширения Scala. Анонимная функция (anonymous function). Альтернативное название функ ционального литерала. Анонимный класс (anonymous class). Синтетический подкласс, сгенериро ванный компилятором Scala из выражения new , в котором вслед за именем класса или трейта идут фигурные скобки. В них содержится тело анонимного подкласса, которое может быть пустым. Но если имя, указанное после выражения new , ссылается на трейт или класс с абстрактными членами, то эти члены необходимо конкретизировать внутри фигурных скобок, определяющих тело анонимного подкласса. Аргумент (argument). При вызове функции каждому ее параметру соответ ствует передаваемый аргумент. Параметр — это переменная, которая ссылается на аргумент. Аргумент — это объект, который передается во время вызова. Кроме того, приложения могут принимать аргументы (командной строки), содержащиеся в массиве Array[String] , который передается методам main объектоводиночек. Глоссарий 589 Блок (block). Одно или несколько выражений или объявлений, заключен ных в фигурные скобки. При вычислении блока все его выражения и объявления обрабатываются по очереди, после чего блок возвращает в качестве результата значение последнего выражения. Блоки обычно используются в качестве тел функции, выражений for , циклов while и в любых других местах, где нужно сгруппировать какоето коли чество инструкций. Если говорить более формально, то блок — это конструкция для инкапсуляции, в которой видны только побочные эффекты и итоговое значение. Таким образом, фигурные скобки, в ко торых определяется класс или объект, не формируют блок, поскольку поля и методы (определенные внутри этих скобок) видны снаружи. Такие фигурные скобки составляют шаблон. Вариантность (variance). Параметр типа для класса или трейта можно поме тить с помощью аннотации вариантности: либо как ковариантный ( + ), либо как контравариантный ( - ). Такие аннотации определяют, как у обобщенного класса или трейта работает отношение наследования. Например, обобщенный класс List ковариантен в своем параметре типа, поэтому List[String] является подтипом List[Any] . По умол чанию, если не указывать аннотации + или - , то параметры типов нонвариантны. Виртуальная машина Java (Java Virtual Machine, JVM). Среда выполнения, в которой работают программы на языке Scala. Возврат (return). В программах на языке Scala функция возвращает зна чение. Вы можете называть его результатом функции. Кроме того, можно сказать, что функция приводит к значению. Результат любой функции в Scala — объект. Вспомогательная функция (helper function). Предназначена для предостав ления какихлибо возможностей для одной или нескольких других функций поблизости. Вспомогательные функции часто реализуются как локальные. Вспомогательный конструктор (auxiliary constructor). Внутри фигурных скобок с определением класса можно указывать дополнительные конструкторы, которые выглядят как определения методов с именем this , но без результирующего типа. Вспомогательный метод (helper method). Вспомогательная функция, явля ющаяся членом класса. Вспомогательные методы зачастую являются приватными. 590 Глоссарий Выдача (yield). Выражение может выдавать (yield) результат. Ключевое слово yield обозначает результат выражения for Вызов (invoke). Вы можете вызвать метод, функцию или замыкание с ар гументами, то есть при выполнении их тела будут использованы за данные аргументы. Выражение (expression). Любой фрагмент кода в Scala дает какойлибо ре зультат. Можно сказать, что результат вычисляется из выражения или выражение возвращает значение. Выражение генератора (generator expression). Генерирует последователь ность значений в выражении for . Например, в for(i <- 1 to 10) вы ражением генератора выступает 1 to 10 Выражение фильтра (filter expression). Булево выражение, которое идет за инструкцией if в выражении for . В for(i <- 1 to 10; if i % 2 == 0) фильтрующим выражением выступает i % 2 == 0 Генератор (generator). Определяет именованное значение и последовательно присваивает ему результаты выражения for . Например, в for(i <- 1 to 10) генератором выступает i <- 1 to 10 . Значение справа от <- — это выражение генератора. Замыкание (closure). Функциональный объект, который захватывает свобод ные переменные и как будто замыкается вокруг переменных, видимых в момент его создания. Значение (value). Результат любого вычисления или выражения в Scala. При этом все значения в Scala являются объектами. Значение, в сущ ности, — это образ объекта в памяти (в куче JVM или стеке). Императивный стиль (imperative style). В этом стиле программирования акцент делается на том, в какой последовательности выполняются операции, чтобы их побочные эффекты проявились в правильном по рядке. Этот стиль характеризуется итерацией с циклами, изменением данных без копирования и методами с побочными эффектами. Эта парадигма доминирует в таких языках, как C, C++, C# и Java, контра стируя с функциональным стилем. Инвариант (invariant). Термин имеет два значения. Это может быть свой ство, которое всегда остается истинным при условии, что структура данных имеет правильный формат. Например, инвариантом отсор тированного двоичного дерева может быть требование, согласно ко торому каждый узел должен быть упорядочен перед своим правым подузлом, если таковой имеется. Термин «инвариант» также иногда Глоссарий 591 служит синонимом нонварианта: «класс Array является инвариантным в своем параметре типа». Инициализация (initialize). При определении переменной в исходном коде Scala вы должны инициализировать ее с помощью какоголибо объекта. Инструкция (statement). Выражение, определение или импорт, то есть дей ствия, которые могут быть заданы в шаблоне или блоке исходного кода Scala. Карринг (currying). Способ написания функций с несколькими списками параметров. Например, def f(x: Int)(y: Int) — это каррированная функция с двумя списками параметров. Чтобы применить карриро ванную функцию, ей нужно передать несколько списков аргументов, как в f(3)(4) . Но мы можем также выполнить частичное применение каррированной функции, как в f(3) Класс (class). Определяется с помощью ключевого слова class и может быть либо абстрактным, либо конкретным. Во время создания экземпляра класса его можно параметризовать с указанием типов и значений. В выражении new Array[String](2) создается экземпляр класса Array , а тип получаемого значения — Array[String] . Класс, который прини мает параметры типов, называется конструктором типа. У типа тоже может быть класс — например, классом типа Array[String] является Array Класс-компаньон (companion class). Класс с тем же именем, что и у объек тасинглтона, определенного в том же исходном файле. Это класс компаньон объектаодиночки. Ковариантность (covariant). Аннотацию ковариантности можно приме нить к параметру типа в классе или трейте, указав перед ним знак плюс ( + ). В результате класс или трейт формирует ковариантные вза имоотношения с аннотированным параметром типа (направленные в ту же сторону). Например, если класс List является ковариантным в своем первом параметре типа, то List[String] будет подтипом List[Any] Композиция примесей (mixin composition). Процесс примешивания трейтов в классы или другие трейты. От традиционного множественного на следования композиция примесей отличается тем, что тип ссылки super неизвестен в момент определения трейта и определяется каждый раз, когда трейт примешивается в класс или в другой трейт. Конструктор типа (type constructor). Класс или трейт, принимающий па раметры типов. 592 Глоссарий Контравариантность (contravariant). Аннотацию контравариантности можно применить к любому параметру типа в классе или трейте, указав перед ним знак минус ( - ). В результате класс или трейт фор мирует контравариантные взаимоотношения с аннотированным па раметром типа (направленные в противоположную от него сторону). Например, если класс Function1 является контравариантным в своем первом параметре типа, то Function1[Any, Any] будет подтипом Function1[String, Any] Литерал (literal). 1 , "Один" и (x: Int) => x + 1 — это все примеры литералов. Это краткое описание объекта, которое в точности отражает структуру созданного объекта. Локальная переменная (local variable). val или var , определенные внутри блока. Параметры функции, несмотря на свою схожесть с локальными переменными, таковыми не считаются; их называют просто параме трами или переменными. Локальная функция (local function). Функция, определенная внутри блока. Тогда как функция, определенная как член класса, трейта или объекта одиночки, называется методом. Метапрограммирование (metaprogramming). Вид программирования, в котором программы принимают на вход другие программы. Ком пиляторы и инструменты наподобие scaladoc являются метапро граммами. Средства метапрограммирования необходимы для работы с аннотациями. Метод (method). Функция, которая является членом какогото класса, трей та или объектаодиночки. Метод без параметров (parameterless method). Это функция без параметров, которая является членом класса, трейта или объектаодиночки. Множественные определения (multiple definitions). Одно и то же выражение может быть присвоено в нескольких определениях, если используется синтаксис вида val v1, v2, v3 = exp Модификатор (modifier). Ключевое слово, которое какимто образом огра ничивает определение класса, трейта, поля или метода. Например, модификатор private говорит о том, что определяемый класс, трейт, поле или метод является приватным. Недоступность (unreachable). На уровне Scala объекты могут становиться недоступными; в этом случае среда выполнения может освободить память, которую они занимают. Недоступность вовсе не означает от Глоссарий 593 сутствие ссылок. Ссылочные типы (экземпляры AnyRef ) реализуются в виде объектов, размещенных в куче JVM. Когда экземпляр ссылоч ного типа становится недоступным, на него и в самом деле ничего не ссылается, что позволяет сборщику мусора его освободить. Типы значений (экземпляры AnyVal ) реализуются как с помощью примитив ных типов, так и в виде типовоберток (таких, как java.lang.In teger ), которые находятся в куче. Экземпляры типов значений могут упако вываться (превращаться из примитивного значения в объектобертку) и распаковываться (превращаться из объектаобертки в примитивное значение) на протяжении существования переменной, которая на них ссылается. Если экземпляр типа значения, который в настоящий момент представлен объектомоберткой в куче JVM, становится не доступным, то на него и в самом деле ничего не ссылается, что позво ляет сборщику мусора его освободить. Но если в настоящий момент значение имеет примитивный тип и становится недоступным, то это не означает, что на него больше ничего не ссылается, поскольку он больше не существует в виде объекта в куче JVM. Среда выполнения может освободить память, занятую недоступными объектами. Но если тип Int , к примеру, реализован во время выполнения как примитивное значение int из языка Java и занимает какуюто память в стековом фрейме выполняющегося метода, то эта память освобождается, только когда фрейм достается из стека при завершении метода. Память для ссылочных типов, таких как String , может быть освобождена сборщи ком мусора JVM после того, как они станут недоступными. Неизменяемость (immutable). Объект является неизменяемым, если после того, как он был создан, его значение нельзя изменить никаким види мым для клиентов образом. Объекты могут быть неизменяемыми, но не обязательно. Нонвариант (nonvariant). Параметр типа класса или трейта по умолчанию является нонвариантом. Когда этот параметр меняется, класс или трейт не производит подтип. Например, класс Array является нонвари антом в своем параметре типа, поэтому Array[String] нельзя считать ни подтипом, ни супертипом Array[Any] Обобщенный класс (generic class). Класс, принимающий параметры типа. На пример, класс scala.List принимает параметр типа, поэтому является обобщенным. Обобщенный трейт (generic trait). Трейт, принимающий параметры типа. Например, трейт scala.collection.Set принимает параметр типа, поэтому является обобщенным. |