Как протестировать код, который зависит от сложных API (например, Amazon S3)?

13

Я борюсь с тестированием метода, который загружает документы в Amazon S3, но я думаю, что этот вопрос относится к любой нетривиальной зависимости API / external. У меня есть только три возможных решения, но ни одно из них не выглядит удовлетворительным:

  1. Запустите код, фактически загрузите документ, проверьте с помощью API AWS, что он был загружен, и удалите его в конце теста. Это сделает тест очень медленным, будет стоить денег при каждом запуске теста и не всегда будет давать тот же результат.

  2. Макет S3. Это супер волосато, потому что я понятия не имею о внутренностях этого объекта, и это неправильно, потому что это слишком сложно.

  3. Просто убедитесь, что MyObject.upload () вызывается с правильными аргументами, и верьте, что я правильно использую объект S3. Это беспокоит меня, потому что нет никакого способа узнать наверняка, что я правильно использовал S3 API только из тестов.

Я проверил, как Amazon тестирует свой собственный SDK, и они все высмеивают. У них есть помощник 200 строк, который делает насмешки. Я не чувствую, что для меня практично делать то же самое.

Как мне это решить?

подпружиненным
источник
Не ответ, но на практике мы используем три подхода, которые вы раскрыли.
июля

Ответы:

28

Есть две проблемы, которые мы должны рассмотреть здесь.

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

Во-вторых, вы смешиваете вызовы стороннего кода с вашей бизнес-логикой, создавая проблемы тестирования и, возможно, делая ваш код более хрупким.

Модульные тесты должны быть быстрыми и часто выполняться. Имитация зависимостей помогает поддерживать эти тесты быстрыми, но потенциально может создавать дыры в покрытии, если зависимость меняется, а макет - нет. Ваш код может быть поврежден, в то время как ваши тесты все еще работают зеленым. Некоторые библиотеки-насмешки будут предупреждать вас, если интерфейс зависимостей изменится, другие - нет.

Интеграционные тесты, с другой стороны, предназначены для тестирования взаимодействия между компонентами, включая сторонние библиотеки. На этом уровне тестирования не следует использовать макеты, потому что мы хотим видеть, как фактический объект взаимодействует вместе. Поскольку мы используем реальные объекты, эти тесты будут медленнее, и мы не будем запускать их почти так же часто, как наши модульные тесты.

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

Одно правило, которое люди считают ценным в отношении насмешек, - не издеваться над типами, которыми вы не владеете . Amazon владеет API для S3, чтобы они могли убедиться, что он не изменится под ними. У вас, с другой стороны, нет таких гарантий. Таким образом, если в своих тестах вы смоделируете S3 API, он может измениться и сломать ваш код, в то время как все ваши тесты покажутся зелеными. Так как же нам тестировать код, который использует сторонние библиотеки?

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

Теперь к коду, который вызывает S3 API. Нам нужно заменить эти вызовы вызовами к PersistenceServiceобъекту. Мы используем внедрение зависимостей, чтобы передать наш PersistenceServiceобъект. Важно не просить S3PersistenceService, а просить PersistenceService. Это позволяет нам менять реализацию во время наших тестов.

Весь код, который использовался для непосредственного использования API S3, теперь использует наш PersistenceService, а наш S3PersistenceServiceтеперь выполняет все вызовы API S3. В наших тестах мы можем макетировать PersistenceService, так как мы им владеем, и использовать макет, чтобы убедиться, что наш код делает правильные вызовы. Но теперь это оставляет, как проверить S3PersistenceService. У него та же проблема, что и раньше: мы не можем тестировать его без вызова внешней службы. Итак ... мы не проводим модульное тестирование. Мы могли бы смоделировать зависимости S3 API, но это придаст нам почти никакой дополнительной уверенности. Вместо этого мы должны проверить это на более высоком уровне: интеграционные тесты.

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

Следующим шагом является написание интеграционных тестов для S3PersistenceService. Они должны быть разделены по имени или папке, чтобы мы могли запускать их отдельно от наших быстрых модульных тестов. Интеграционные тесты часто могут использовать те же каркасы тестирования, что и модульные тесты, если код достаточно информативен, поэтому нам не нужно изучать новый инструмент. Фактический код для интеграционного теста - это то, что вы написали бы для своего варианта 1.

cbojar
источник
вопрос в том, как вы запускаете интеграционные или, точнее, тесты e2e для предоставляемого вами API. Вы не можете издеваться над PersistenceService по очевидным причинам. Либо я что-то не так понял, либо добавление еще одного слоя между API приложения и AWS API не дает вам ничего, кроме более легкого проведения модульных тестов
Еркен,
@Yerken Пока я думаю об этом, я почти уверен, что смогу дать еще один длинный ответ на этот вопрос. Это может даже стоить вам, потому что вы можете получить больше, чем просто мой ответ.
cbojar
4

Вы должны сделать оба.

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

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

Первые тесты проверяют, что ваш код действительно работает с S3, вторые тесты, что ваш код правильно вызывает код, который общается с S3.

JDT
источник
2

Я бы сказал, что это зависит от сложности использования вами API .

  1. Вам определенно необходимо выполнить хотя бы некоторое тестирование, которое фактически вызывает API S3 и подтверждает, что оно работает от начала до конца.

  2. Вам также определенно необходимо провести дополнительное тестирование, которое на самом деле не вызывает API, поэтому вы можете адекватно протестировать свое собственное программное обеспечение, не вызывая API все время.

Остается вопрос: нужно ли издеваться над API?

И я думаю, что это зависит от того, сколько вы с этим делаете. Если вы выполняете только одно или два простых действия, я не думаю, что вам нужно заниматься всеми проблемами макета. Я был бы удовлетворен только проверкой моего использования функций и проведением некоторого живого тестирования.

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


источник
1

В дополнение к предыдущим ответам, основной вопрос заключается в том, хотите ли вы (и каким образом) высмеивать API S3 для своих тестов.

Вместо того, чтобы вручную высмеивать отдельные ответы S3, вы можете воспользоваться некоторыми очень сложными существующими средами насмешки. Например, moto предоставляет функциональность, которая очень похожа на фактический S3 API.

Вы также можете взглянуть на LocalStack , платформу, которая объединяет существующие инструменты и предоставляет полностью функциональную локальную облачную среду (включая S3), которая облегчает интеграционное тестирование.

Хотя некоторые из этих инструментов написаны на других языках (Python), должно быть легко раскрутить тестовую среду во внешнем процессе из ваших тестов, скажем, в Java / JUnit.

whummer
источник