Файл: Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.12.2023
Просмотров: 758
Скачиваний: 11
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Резюме
1 ... 56 57 58 59 60 61 62 63 64
575
Поскольку в Java изменяемые и неизменяемые коллекции по типам не раз
личаются, преобразование из, скажем, collection.immutable.List выдаст коллекцию java.util.List
. При всех попытках применить операции по внесению изменений в отношении этой коллекции будет генерироваться исключение
UnsupportedOperationException
, например:
scala> val jul: java.util.List[Int] = List(1, 2, 3)
val jul: java.util.List[Int] = [1, 2, 3]
scala> jul.add(7)
java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:131)
Резюме
Теперь вы получили более детальное представление об использовании кол
лекций Scala. В них применен подход, предоставляющий вам целый ряд не просто полезных специализированных методов, но понастоящему эффек
тивных строительных блоков. Сочетание двух или трех таких строительных блоков позволит провести множество полезных вычислений. Эффективность стиля, принятого в библиотеке, наиболее ярко проявляется благодаря име
ющемуся в Scala облегченному синтаксису для функциональных литералов, а также благодаря тому, что сам язык предоставляет множество типов кол
лекций, сохраняющих постоянство и неизменяемость. В следующей, заклю
чительной главе мы рассмотрим утверждения и модульное тестирование.
25
Утверждения и тесты
Утверждения и тесты — два важных способа проверки правильности по
ведения созданных вами программных средств. В данной главе мы покажем несколько вариантов для их создания и запуска, доступных в Scala.
25 .1 . Утверждения
Утверждения в Scala создаются в виде вызовов предопределенного метода assert
1
. Выражение assert(condition)
выдает ошибку
AssertionError
, если
условие condition не соблюдается. Существует также версия assert
, ис
пользующая два аргумента: выражение assert(condition,
explanation)
те
стирует условие. При его несоблюдении оно выдает ошибку
AssertionError
, в сообщении о которой содержится заданное объяснение explanation
. Тип объяснения —
Any
, поэтому в качестве объяснения может передаваться любой объект. Чтобы поместить в
AssertionError строковое объяснение, метод assert будет вызывать в отношении этого объекта метод toString
Например, в метод по имени above
, класса
Element и показанный в листин
ге 10.13, assert можно поместить после вызовов widen
, чтобы убедиться в одинаковой ширине расширенных элементов. Этот вариант показан в листинге 25.1.
Листинг 25.1. Использование утверждения def above(that: Element): Element =
val this1 = this widen that.width
1
Метод assert определен в объектеодиночке
Predef
, элементы которого автомати
чески импортируются в каждый исходный файл программы на языке Scala.
25 .1 . Утверждения 577
val that1 = that widen this.width assert(this1.width == that1.width)
elem(this1.contents ++ that1.contents)
Выполнить ту же задачу можно и подругому: проверить ширину в конце метода widen
, непосредственно перед возвращением значения. Этого можно добиться, сохранив результат в val
переменной, выполняя утверждение применительно к результату с последующим указанием val
переменной, чтобы результат возвращался в том случае, если утверждение было успешно подтверждено. Но, как показано в листинге 25.2, это можно сделать более выразительно, воспользовавшись довольно удобным методом ensuring из объектаодиночки
Predef
Листинг 25.2. Использование метода ensuring для утверждения результата выполнения функции private def widen(w: Int): Element =
if w <= width then this else {
val left = elem(' ', (w - width) / 2, height)
var right = elem(' ', w — width - left.width, height)
left beside this beside right
} ensuring (w <= _.width)
Благодаря неявному преобразованию метод ensuring может использоваться с любым результирующим типом. В данном коде все выглядит так, словно ensuring вызван в отношении результата выполнения метода widen
, име
ющего тип
Element
. Однако на самом деле ensuring вызван в отношении типа, в который
Element был автоматически преобразован. Метод ensuring получает один аргумент, функциюпредикат, которая берет результирующий тип, возвращает булево значение и передает результат предикату. Если пре
дикат возвращает true
, то метод ensuring возвращает результат, в противном случае метод выдаст ошибку
AssertionError
В данном примере предикат имеет вид w
<=
_.width
. Знак подчеркивания является заместителем для одного аргумента, который передается преди
кату, а именно результата типа
Element
, получаемого от метода widen
. Если ширина, переданная в виде w
методу widen
, меньше ширины результата типа
Element или равна ей, то предикат будет вычислен в true и результатом ensuring будет объект типа
Element
, в отношении которого он был вызван.
Данное выражение в методе widen последнее, поэтому его результат типа
Element и будет значением, возвращаемым самим методом widen
Утверждения могут включаться и выключаться с помощью флагов команд
ной строки JVM
-ea и
-da
. Когда флаги включены, каждое утверждение
578 Глава 25 • Утверждения и тесты служит небольшим тестом, использующим фактические данные, вычислен
ные при выполнении программы. Далее мы сфокусируемся на написании внешних тестов, которые предоставляют собственные тестовые данные и выполняются независимо от приложения.
25 .2 . Тестирование в Scala
Scala содержит много опций для тестирования, начиная с хорошо известно
го инструментария Java, такого как JUnit и TestNG, и заканчивая инстру
ментарием, написанным на Scala, например ScalaTest, specs2 и ScalaCheck.
В оставшейся части главы мы дадим краткий обзор этого инструментария.
Начнем со ScalaTest.
ScalaTest — наиболее гибкая среда тестирования в Scala, это средство можно легко настроить на решение различных задач. Гибкость ScalaTest означает, что команды могут использовать тот стиль тестирования, который более пол
но отвечает их потребностям. Например, для команд, знакомых с JUnit, наи
более удобным станет стиль
AnyFunSuite
. Пример показан в листинге 25.3.
Листинг 25.3. Написание тестов с использованием AnyFunSuite import org.scalatest.funsuite.AnyFunSuite import Element.elem class ElementSuite extends AnyFunSuite:
test("elem result should have passed width") {
val ele = elem('x', 2, 3)
assert(ele.width == 2)
}
Центральная концепция в ScalaTest — набор, то есть коллекция, тестов.
Тестом может являться любой код с именем, который может быть запущен и завершен успешно или неуспешно, отложен или отменен. Центральный блок композиции в ScalaTest — трейт
Suite
. В нем объявляются методы жизненного цикла, определяющие исходный способ запуска тестов, ко
торый можно переопределить под нужные способы написания и запуска тестов.
В ScalaTest предлагаются стилевые трейты, расширяющие
Suite и пере
определяющие методы жизненного цикла для поддержания различных стилей тестирования. В этой среде также предоставляются примешиваемые
трейты, которые переопределяют методы жизненного цикла таким образом,
25 .3 . Информативные отчеты об ошибках 579
чтобы те отвечали конкретным потребностям тестирования. Классы тестов определяются сочетанием стиля
Suite и примешиваемых трейтов, а тестовые наборы — путем составления экземпляров
Suite
Пример стиля тестирования —
AnyFunSuite
, расширенный тестовым клас
сом, показанным в листинге 25.3. Слово
Fun в
AnyFunSuite означает функ
цию, а test является определенным в
AnyFunSuite методом, который вы
зывается первичным конструктором
ElementSuite
. Вы указываете название теста в виде строки в круглых скобках, а сам код теста помещаете в фи
гурные скобки. Код теста является функцией, передаваемой методу test в виде параметра по имени, которая регистрирует его для последующего выполнения.
Среда ScalaTest интегрирована в широко используемые инструментальные средства сборки, такие как sbt и Maven, и в IDE, например IntelliJIDEA и Eclipse.
Suite можно запустить и непосредственно через приложение
ScalaTest Runner или из интерпретатора Scala, просто вызвав в отношении его метод execute
. Соответствующий пример выглядит так:
scala> (new ElementSuite).execute()
ElementSuite:
- elem result should have passed width
Все стили ScalaTest, включая
AnyFunSuite
, предназначены для стимуляции написания специализированных тестов с осмысленными названиями. Кроме того, все стили создают вывод, похожий на спецификацию, которая может облегчить общение между заинтересованными сторонами. Выбранным вами стилем предписывается только то, как будут выглядеть объявления ваших тестов. Все остальное в ScalaTest работает одинаково, независимо от выбранного стиля
1 25 .3 . Информативные отчеты об ошибках
В тесте, показанном в листинге 25.3, предпринимается попытка создать элемент шириной, равной 2, и высказывается утверждение, что ширина получившегося элемента действительно равна 2. Если утверждение не под
твердится, то отчет об ошибке будет включать имя файла и номер строки с неоправдавшимся утверждением, а также информативное сообщение об ошибке:
1
Более подробную информацию о ScalaTest можно найти на сайте www.scalatest.org.
580 Глава 25 • Утверждения и тесты scala> val width = 3
width: Int = 3
scala> assert(width == 2)
org.scalatest.exceptions.TestFailedException:
3 did not equal 2
Чтобы обеспечить содержательные сообщения об ошибках при неверных утверждениях, ScalaTest в ходе компиляции анализирует выражения, пере
данные каждому вызову утверждения. Если вы хотите увидеть более по
дробную информацию о неверных утверждениях, то можете воспользоваться имеющимся в ScalaTest средством Diagrams, сообщения об ошибках которого показывают схему выражения, переданного утверждению assert
:
scala> assert(List(1, 2, 3).contains(4))
org.scalatest.exceptions.TestFailedException:
assert(List(1, 2, 3).contains(4))
| | | | | |
| 1 2 3 false 4
List(1, 2, 3)
Имеющиеся в ScalaTest методы assert не делают разницы в сообщениях об ошибках между фактическим и ожидаемым результатами. Они просто по
казывают, что левый операнд не равен правому, или показывают значения на схеме. Если нужно подчеркнуть различия между фактическим и ожидаемым результатами, то можно воспользоваться имеющимся в ScalaTest альтерна
тивным методом assertResult
:
assertResult(2) {
ele.width
}
С помощью данного выражения показывается, что от кода в фигурных скобках ожидается результат
2
. Если в результате выполнения этого кода получится
3
, то в отчете об ошибке тестирования будет показано сообщение
Expected
2
, but got
3
(Ожидалось 2, но получено 3).
При необходимости проверить, выдает ли метод ожидаемое исключение, можно воспользоваться имеющимся в ScalaTest методом assertThrows
:
assertThrows[IllegalArgumentException] {
elem('x', -2, 3)
}
Если код в фигурных скобках выдает не то исключение, которое ожидалось, или вообще не выдает его, то метод assertThrows тут же завершит свою
25 .4 . Использование тестов в качестве спецификаций 581
работу и выдаст исключение
TestFailedException
. А отчет об ошибке будет содержать сообщение с полезной для вас информацией:
Expected IllegalArgumentException to be thrown,
but NegativeArraySizeException was thrown.
Но если код завершится внезапной генерацией экземпляра переданного класса исключения, то метод assertThrows выполнится нормально. При не
обходимости провести дальнейшее исследование ожидаемого исключения можно вместо assertThrows воспользоваться методом перехвата intercept
Он работает аналогично методу asassertThrows
, за исключением того, что при генерации ожидаемого исключения intercept возвращает это исклю
чение:
val caught =
intercept[ArithmeticException] {
1 / 0
}
assert(caught.getMessage == "/ by zero")
Короче говоря, имеющиеся в ScalaTest утверждения стараются выдать по
лезные сообщения об ошибках, способные помочь вам диагностировать и устранить проблемы в коде.
25 .4 . Использование тестов в качестве спецификаций
В стиле тестирования при разработке через реализацию поведения (behavior
driven development, BDD) основной упор делается на написание легко воспринимаемых человеком спецификаций расширенного поведения кода и сопутствующих тестов, которые проверяют, свойственно ли коду такое по
ведение. В ScalaTest включены несколько трейтов, содействующих этому сти
лю тестирования. Пример использования одного такого трейта,
AnyFlatSpec
, показан в листинге 25.4.
Листинг 25.4. Спецификация и тестирование поведения с помощью
AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import Element.elem class ElementSpec extends AnyFlatSpec, Matchers:
582 Глава 25 • Утверждения и тесты "A UniformElement" should
"have a width equal to the passed value" in {
val ele = elem('x', 2, 3)
ele.width should be (2)
}
it should "have a height equal to the passed value" in {
val ele = elem('x', 2, 3)
ele.height should be (3)
}
it should "throw an IAE if passed a negative width" in {
an [IllegalArgumentException] should be thrownBy {
elem('x', -2, 3)
}
}
При использовании
AnyFlatSpec тесты создаются в виде директив специфи-
кации. Сначала в виде строки пишется название тестируемого субъекта
(
"A
Uni formElement"
в листинге 25.4), затем should
, или must
, или can
(что означает «обязан», или «должен», или «может» соответственно), потом строка, обозначающая характер поведения, требуемого от субъекта, а затем ключевое слово in
. В фигурных скобках, стоящих после in
, пишется код, тестирующий указанное поведение. В последующих директивах, чтобы со
слаться на самый последний субъект, можно написать it
. При выполнении
AnyFlatSpec этот трейт будет запускать каждую директиву спецификации в виде теста ScalaTest.
AnyFlatSpec
(и другие специфицирующие трейты, которые есть в ScalaTest) генерирует вывод, который при запуске читается как спецификация. Например, вот на что будет похож вывод, если запустить
ElementSpec из листинга 25.4 в интерпретаторе:
scala> (new ElementSpec).execute()
A UniformElement
- should have a width equal to the passed value
- should have a height equal to the passed value
- should throw an IAE if passed a negative width
В листинге 25.4 также показан имеющийся в ScalaTest предметноориенти
рованный язык (domainspecific language, DSL) выявления соответствий.
Примешиванием трейта
Matchers можно создавать утверждения, которые при чтении больше похожи на естественный язык. Имеющийся в ScalaTest
DSLязык предоставляет множество средств выявления соответствий, кроме этого, позволяет определять новые предоставленные пользователем средства выявления соответствий, содержащие сообщения об ошибках. Такие сред
ства, показанные в листинге 25.4, включают синтаксис should be и an
[...]
should be thrownBy
:
...}
. Как вариант, если предпочтение отдается глаголу