litbaza книги онлайнРазная литератураCrystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 59 60 61 62 63 64 65 66 67 ... 75
Перейти на страницу:
один из них, поскольку он помогает в организации тестов. Однако блок #it необходим, поскольку без него сообщения об ошибках не будут корректно сообщаться.

По мере роста количества кода в приложении будет расти и количество тестов. Это может затруднить отладку конкретных тестовых случаев. В этом случае аргумент focus: true можно добавить в блок #describe или #it. При этом будет выполнена только одна спецификация, как в следующем примере:

it "does something", focus: true do

    1.should eq 1

end

Только не забудьте удалить его перед совершением!

Модуль Spec также предоставляет некоторые дополнительные методы, которые можно использовать для более точного контроля выполнения ваших тестовых случаев. Некоторые из них перечислены здесь:

• #pending: этот метод используется для определения тестового примера для чего-то, что еще не полностью реализовано, но будет реализовано в будущем, например, ожидающий "check cat" { cat.alive? }. Блок метода никогда не выполняется, но может использоваться для описания того, что должен делать тест.

• #pending!: Метод #pending! аналогичен предыдущему методу, но может использоваться для динамического пропуска тестового примера. Это может быть полезно для обеспечения выполнения зависимостей/требований системного уровня перед запуском тестового примера.

• #fail: Наконец, этот метод можно использовать для ручного провала тестового примера. Это можно использовать в сочетании с пользовательской условной логикой для создания более сложных утверждений, с которыми не могут справиться встроенные утверждения.

Маркировка (Tagging) тестов

Теги — это способ организовать спецификации в группы, чтобы можно было выполнить их подмножество. Подобно фокусировке спецификации, теги применяются к блокам #describe или #it через аргумент tags следующим образом:

require "spec"

describe "tags" do

  it "tag a", tags: "a" do

  end

    it "tag b", tags: "b" do

  end

end

Отсюда вы можете использовать опцию --tag через crystal spec, чтобы контролировать, какие из них будут выполняться, как описано здесь:

• --tag 'a' --tag 'b' будет включать спецификации, отмеченные ИЛИ b.

• --tag '~a' --tag '~b' будет включать спецификации, не помеченные знаком И, не помеченные знаком b.

• --tag 'a' --tag '~b' будет включать спецификации, отмеченные тегом a, но не отмеченные тегом b.

Последняя команда может выглядеть так: crystal spec --tag 'a'. Далее мы рассмотрим, как обрабатывать зависимости внутренних объектов путем создания макетов.

Осмеяние (Mocking)

Предыдущий пример с методом #add не имел никаких внешних зависимостей, но помните в Главе 4 «Изучение Crystal посредством написания интерфейса командной строки», как мы сделали NotificationEmitter типом аргумента конструктора, а не использовали его непосредственно в методе #process? Тип NotificationEmitter является зависимостью типа Processor.

Причина, по которой мы сделали его аргументом конструктора, заключается в том, что он следует нашим принципам проектирования SOLID (где SOLID означает принцип единой ответственности, принцип открытости-закрытости, принцип замены Лискова, принцип сегрегации интерфейса и принцип инверсии зависимостей), что, в свою очередь, делает тип легче для тестирования, позволяя использовать фиктивную реализацию вместо этого аргумента. Макет позволяет вам подтвердить, что он вызывается правильно, и настроить его на возврат значений так, чтобы тестовые примеры каждый раз были одинаковыми.

Давайте посмотрим на упрощенный пример здесь:

module TransformerInterface

  abstract def transform(value : String) : String

end

struct ShoutTransformer

  include Transformerinterface

  def transform(value : String) : String

    value.upcase

  end

end

class Processor

  def initialize(@transformer : Transformerinterface =

    ShoutTransformer.new); end

  def process(value : String) : String

    @transformer.transform value

  end

end

puts Processor.new.process "foo"

Здесь у нас есть тип интерфейса Transformer, который определяет требуемый метод, который должен реализовать каждый преобразователь. У нас есть единственная его реализация, ShoutTransformer, которая преобразует значение в верхний регистр. Затем у нас есть тип Processor, который использует тип интерфейса Transformer как часть своего метода #process, по умолчанию использующий преобразователь крика. Запуск этой программы приведет к выводу FOO на ваш терминал.

Поскольку мы хотим протестировать наш тип Processor изолированно, мы собираемся создать имитацию реализации преобразователя для использования в нашем тесте. Это гарантирует, что мы не тестируем больше, чем требуется. Взгляните на следующий пример:

class MockTransformer

  include Transformerinterface

  getter transform_arg_value : String? = nil

  def transform(value : String) : String

    @transform_arg_value = value

  end

end

Он реализует тот же API, что и другие, но фактически не преобразует значение, а просто предоставляет его через переменную экземпляра. Затем мы могли бы использовать это в тесте следующим образом, обязательно потребовав также Processor и MockTransformer, если они не определены в одном файле:

require "spec"

describe Processor do

  describe "#process" do

    it "processes" do

      transformer = MockTransformer.new

      Processor.new(transformer).process "bar"

      transformer.transform_arg_value.should eq "bar"

    end

  end

end

Поскольку фиктивный преобразователь хранит значение, мы можем использовать его, чтобы гарантировать, что он был вызван с ожидаемым значением. Это позволит выявить случаи, когда он не вызывается или вызывается с неожиданным значением, что является ошибкой. Макетная реализация также не обязательно должна быть частной. Его можно было бы представить как часть самого проекта, чтобы конечный пользователь мог использовать его и в своих тестах.

Хуки

Основной принцип тестирования заключается в том, что каждый тестовый пример независим от других, например, не полагаясь на состояние предыдущего теста. Однако для нескольких тестов может потребоваться одно и то же состояние для проверки того, на чем они сосредоточены. Crystal предоставляет несколько методов как часть модуля Spec, которые можно использовать для определения обратных вызовов в определенных точках жизненного цикла теста.

Эти методы могут быть полезны для централизации настройки/удаления необходимого состояния для тестов. Например, предположим, что вы хотите убедиться, что глобальная переменная среды установлена перед запуском любого теста, и в нескольких тестовых случаях есть другая переменная, но нет других тестов. Для этого вы можете использовать методы .before_suite, #before_each и #after_each. Пример этого вы можете увидеть в следующем фрагменте кода:

require "spec"

Spec.before_suite do

  ENV["GLOBAL_VAR"] = "foo"

end

describe "My tests"

1 ... 59 60 61 62 63 64 65 66 67 ... 75
Перейти на страницу:

Комментарии
Минимальная длина комментария - 20 знаков. Уважайте себя и других!
Комментариев еще нет. Хотите быть первым?