Есть две проблемы, которые мы должны рассмотреть здесь.
Во-первых, вы, кажется, смотрите на все свои тесты с точки зрения модульных тестов. Модульные тесты чрезвычайно ценны, но это не единственные виды тестов. Тесты можно разделить на несколько уровней: от очень быстрых модульных тестов до менее быстрых интеграционных тестов и даже более медленных приемочных тестов . (Там может быть еще больше слоев, как функциональные тесты .)
Во-вторых, вы смешиваете вызовы стороннего кода с вашей бизнес-логикой, создавая проблемы тестирования и, возможно, делая ваш код более хрупким.
Модульные тесты должны быть быстрыми и часто выполняться. Имитация зависимостей помогает поддерживать эти тесты быстрыми, но потенциально может создавать дыры в покрытии, если зависимость меняется, а макет - нет. Ваш код может быть поврежден, в то время как ваши тесты все еще работают зеленым. Некоторые библиотеки-насмешки будут предупреждать вас, если интерфейс зависимостей изменится, другие - нет.
Интеграционные тесты, с другой стороны, предназначены для тестирования взаимодействия между компонентами, включая сторонние библиотеки. На этом уровне тестирования не следует использовать макеты, потому что мы хотим видеть, как фактический объект взаимодействует вместе. Поскольку мы используем реальные объекты, эти тесты будут медленнее, и мы не будем запускать их почти так же часто, как наши модульные тесты.
Приемочные испытания смотрят на еще более высокий уровень, проверяя, что требования к программному обеспечению выполнены. Эти тесты выполняются для всей, полной системы, которая будет развернута. Еще раз, никакие насмешки не должны использоваться.
Одно правило, которое люди считают ценным в отношении насмешек, - не издеваться над типами, которыми вы не владеете . Amazon владеет API для S3, чтобы они могли убедиться, что он не изменится под ними. У вас, с другой стороны, нет таких гарантий. Таким образом, если в своих тестах вы смоделируете S3 API, он может измениться и сломать ваш код, в то время как все ваши тесты покажутся зелеными. Так как же нам тестировать код, который использует сторонние библиотеки?
Ну, мы не. Если мы следуем рекомендациям, мы не можем высмеивать объекты, которые нам не принадлежат. Но ... если у нас есть наши прямые зависимости, мы можем их издеваться. Но как? Мы создаем нашу собственную оболочку для S3 API. Мы можем сделать его похожим на S3 API или сделать его более близким к нашим потребностям (предпочтительно). Мы можем даже сделать это немного более абстрактным, скажем, PersistenceService
а не AmazonS3Bucket
. PersistenceService
будет интерфейс с такими методами, как #save(Thing)
и #fetch(ThingId)
, типы методов, которые мы хотели бы видеть (это примеры, вы, возможно, на самом деле хотите разные методы). Теперь мы можем реализовать PersistenceService
API S3 (скажем, a S3PersistenceService
), инкапсулируя его вдали от нашего вызывающего кода.
Теперь к коду, который вызывает S3 API. Нам нужно заменить эти вызовы вызовами к PersistenceService
объекту. Мы используем внедрение зависимостей, чтобы передать наш PersistenceService
объект. Важно не просить S3PersistenceService
, а просить PersistenceService
. Это позволяет нам менять реализацию во время наших тестов.
Весь код, который использовался для непосредственного использования API S3, теперь использует наш PersistenceService
, а наш S3PersistenceService
теперь выполняет все вызовы API S3. В наших тестах мы можем макетировать PersistenceService
, так как мы им владеем, и использовать макет, чтобы убедиться, что наш код делает правильные вызовы. Но теперь это оставляет, как проверить S3PersistenceService
. У него та же проблема, что и раньше: мы не можем тестировать его без вызова внешней службы. Итак ... мы не проводим модульное тестирование. Мы могли бы смоделировать зависимости S3 API, но это придаст нам почти никакой дополнительной уверенности. Вместо этого мы должны проверить это на более высоком уровне: интеграционные тесты.
Это может звучать немного тревожно, говоря, что мы не должны тестировать часть нашего кода, но давайте посмотрим на то, что мы достигли. У нас было много кода повсюду, где мы не могли выполнить модульное тестирование, которое теперь можно тестировать модулем через PersistenceService
. Наш сторонний библиотечный беспорядок ограничен одним классом реализации. Этот класс должен обеспечивать необходимую функциональность для использования API, но к нему не прикреплена внешняя бизнес-логика. Поэтому, как только оно написано, оно должно быть очень стабильным и не должно сильно меняться. Мы можем положиться на более медленные тесты, которые мы не запускаем так часто, потому что код стабилен.
Следующим шагом является написание интеграционных тестов для S3PersistenceService
. Они должны быть разделены по имени или папке, чтобы мы могли запускать их отдельно от наших быстрых модульных тестов. Интеграционные тесты часто могут использовать те же каркасы тестирования, что и модульные тесты, если код достаточно информативен, поэтому нам не нужно изучать новый инструмент. Фактический код для интеграционного теста - это то, что вы написали бы для своего варианта 1.