Главная страница
Навигация по странице:

  • Листинг 25.1.

  • Листинг 25.2.

  • Листинг 25.3.

  • Листинг 25.4.

  • Листинг 25.5.

  • Листинг 25.6.

  • Scala. Профессиональное программирование 2022. Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста


    Скачать 6.24 Mb.
    НазваниеОдерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста
    Дата27.04.2023
    Размер6.24 Mb.
    Формат файлаpdf
    Имя файлаScala. Профессиональное программирование 2022.pdf
    ТипДокументы
    #1094967
    страница61 из 64
    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 предметно­ориенти­
    рованный язык (domain­specific language, DSL) выявления соответствий.
    Примешиванием трейта
    Matchers можно создавать утверждения, которые при чтении больше похожи на естественный язык. Имеющийся в ScalaTest
    DSL­язык предоставляет множество средств выявления соответствий, кроме этого, позволяет определять новые предоставленные пользователем средства выявления соответствий, содержащие сообщения об ошибках. Такие сред­
    ства, показанные в листинге 25.4, включают синтаксис should be и an
    [...]
    should be thrownBy
    :
    ...}
    . Как вариант, если предпочтение отдается глаголу

    25 .4 . Использование тестов в качестве спецификаций 583
    must
    , а не should
    , то можно примешать
    MustMatchers
    . Например, примеши­
    вание
    MustMatchers позволит вам создавать следующие выражения:
    result must be >= 0
    map must contain key 'c'
    Если последнее утверждение не подтвердится, то будет показано сообщение об ошибке следующего вида:
    Map('a' –> 1, 'b' –> 2) did not contain key 'c'
    Среда тестирования specs2 — средство с открытым кодом, написанное на
    Scala Эриком Торреборре (Eric Torreborre), — также поддерживает BDD­
    стиль тестирования, но с другим синтаксисом. Например, specs2 можно ис­
    пользовать для создания теста, показанного в листинге 25.5.
    Листинг 25.5. Спецификация и тестирование поведения с использованием среды specs2
    import org.specs2.*
    import Element.elem object ElementSpecification extends Specification:
    "A UniformElement" should {
    "have a width equal to the passed value" in {
    val ele = elem('x', 2, 3)
    ele.width must be_==(2)
    }
    "have a height equal to the passed value" in {
    val ele = elem('x', 2, 3)
    ele.height must be_==(3)
    }
    "throw an IAE if passed a negative width" in {
    elem('x', -2, 3) must throwA[IllegalArgumentException]
    }
    }
    В specs2, как и в ScalaTest, существует DSL­язык выявления соответствий.
    Некоторые примеры работы средств выявления соответствий, имеющихся в specs2, показаны в листинге 25.5 в строках, содержащих must be_==
    и must throwA
    1
    . Среду specs2 можно использовать в автономном режиме, но она также интегрируется со ScalaTest и JUnit, поэтому specs2­тесты можно за­
    пускать и с этими инструментами.
    1
    Программное средство specs2 можно загрузить с сайта specs2.org.

    584 Глава 25 • Утверждения и тесты
    Одной из основных идей BDD является то, что тесты могут помогать обмени­
    ваться мнениями людям, принимающим решения о характере поведения про­
    граммных средств, людям, разрабатывающим программные средства, и людям, определяющим степень завершенности и работоспособности программных средств. В этом ключе могут применяться любые стили, имеющиеся в ScalaTest или specs2, однако в ScalaTest есть специально разработанный для этого стиль
    AnyFeatureSpec
    . Пример его использования показан в листинге 25.6.
    Листинг 25.6. Использование тестов для содействия обмену мнениями среди всех заинтересованных сторон import org.scalatest.*
    import org.scalatest.featurespec.AnyFeatureSpec class TVSetSpec extends AnyFeatureSpec, GivenWhenThen:
    Feature("TV power button") {
    Scenario("User presses power button when TV is off") {
    Given("a TV set that is switched off")
    When("the power button is pressed")
    Then("the TV should switch on")
    pending
    }
    }
    Стиль
    AnyFeatureSpec разработан для того, чтобы направить в нужное русло обсуждений предназначение программных средств: вам следует выявить специ фические требования, а затем дать им точное определение в виде
    скриптов. Сосредоточиться на переговорах об особенностях отдельно взятых скриптов помогают методы
    Given
    ,
    When и
    Then
    , предоставляемые трейтом
    GivenWhenThen
    . Вызов pending в самом конце показывает: и тест, и само по­
    ведение не реализованы, имеется лишь спецификация. Как только будут реализованы все тесты и конкретные действия, тесты будут пройдены и тре­
    бования можно будет посчитать выполненными.
    25 .5 . Тестирование на основе свойств
    Еще одним полезным средством тестирования для Scala является Sca­
    laCheck — среда с открытым кодом, созданная Рикардом Нильсоном (Rickard
    Nilsson). Она позволяет указывать свойства, которыми должен обладать тестируемый код. Для каждого свойства ScalaCheck создает данные и вы­
    дает утверждения, проверяющие наличие тех или иных свойств. Пример использования ScalaCheck из ScalaTest
    AnyWordSpec
    , в который примешан трейт
    ScalaCheckPropertyChecks
    , показан в листинге 25.7.

    25 .5 . Тестирование на основе свойств
    1   ...   56   57   58   59   60   61   62   63   64


    написать администратору сайта