В Poll Everywhere мы тестируем наше приложение Rails с помощью RSpec , популярного фреймворка для разработки на основе поведения (BDD) на Ruby. Мы широко используем DSL RSpec для настройки тестов и ожидаем, чтобы наши тесты были понятными и поддерживаемыми, но только изредка мы использовали общие контексты RSpec. В недавнем проекте я экспериментировал с общими контекстами, чтобы посмотреть, могут ли они уменьшить шаблонный код в тестах, и в целом был удовлетворен результатами, но также обнаружил подлую ловушку!

Общие контексты в RSpec
В RSpec, каждый пример работает в контексте: данные и конфигурация для примера , чтобы опираться на, в том числе и жизненный цикл крючков, letи subjectдеклараций, а также вспомогательные методов. Эти контексты наследуются (и могут быть переопределены) вложенными группами примеров. Однако так же, как наследование объектов не всегда является правильной моделью для совместного использования кода , наследование контекста не всегда является правильной моделью для совместного использования контекста. Общие контексты RSpec действуют как модули Ruby, позволяя повторно использовать контекст между группами примеров, которые не образуют отношения родитель-потомок.

Как и общие примеры , общие контексты чрезвычайно полезны для уменьшения беспорядка и дублирования в спецификациях. Например, рассмотрим контроллер Rails, который имеет несколько разных действий (скажем, showи update), но всегда требует, чтобы пользователь вошел в систему, и может реагировать по-разному в зависимости от разрешений этого пользователя. В общем, эти действия ведут себя по-разному, поэтому общие примеры неуместны, но было бы сложно воссоздать каждый отдельный тип пользователя для каждого отдельного действия. Это идеальный случай для общих контекстов:


Совместное использование контекстов по метаданным
Как показано в приведенном выше примере, общие контексты определяются с помощью shared_contextи добавляются в контекст группы примера с помощью include_context. Однако общие контексты также могут быть добавлены в контекст с помощью метаданных . При определении общего контекста передайте символ в качестве второго аргумента в shared_context (после имени контекста). Любой пример или группа примеров, которые имеют тот же символ, что и второй аргумент, автоматически включают общий контекст.

(Чтобы быть более точным, RSpec на самом деле ожидает и хранит метаданные в виде хэша, поэтому вы можете передать хеш и сохранить несколько пар ключ-значение в метаданных как для shared_contextопределений, так и для примеров и групп примеров. Для удобства RSpec интерпретирует символы на их принадлежат как ключи со значением по умолчанию true. Как бы вы ни определяли метаданные, RSpec автоматически включит общий контекст, если любая из пар ключ-значение в метаданных примера совпадает с любой из пар ключ-значение в метаданных общего контекста.)

Компромиссы и рекомендации
Совместное использование контекста с помощью метаданных удобно, потому что оно настолько краткое: минимальное количество нажатий клавиш, отсутствие необходимости в дополнительном вложении и очень модульная конструкция. Однако у него есть один большой недостаток: ключи метаданных используются глобально. Если два разных общих контекста используют один и тот же ключ метаданных и определяют одно и то же let, например, как два разных значения, то фактическое значение этого let будет зависеть от порядка выполнения.

Мы столкнулись с проблемой, когда два разных файла спецификаций определяли свои собственные общие контексты с одинаковыми lets. Когда эти две спецификации выполнялись в одном процессе (мы используем parallel-rspecдля распараллеливания нашего набора тестов), одна из них выйдет из строя, по-видимому, без причины. Это происходило достаточно спорадически, и нам потребовалось почти два месяца, чтобы понять, что происходит.

В результате Poll Everywhere принял соглашение о том, что мы используем метаданные только для обмена контекстами, которые необходимы в нескольких различных спецификациях (например, насмешка запросов для нашего платежного процессора или что-либо еще в spec/support). Для общих контекстов, ограниченных по области действия одним файлом спецификации, мы явно добавляем контекст с помощью include_context.

Вывод
Общие контексты - хороший способ очистить шаблонную настройку в тестах RSpec, особенно когда эта настройка затрагивает несколько контекстов. Совместное использование контекстов с метаданными удобно для написания тестов и может сделать описания тестов легкими для чтения и понимания, но будьте осторожны с их глобальным охватом. Как и в случае с любым другим инструментом, ваш опыт может отличаться, но я надеюсь, что с общими контекстами и метаданными вы сможете писать более короткие и четкие тесты без головной боли, связанной с загадочно непоследовательной CI.