Как мне написать тесты на соответствие сервису?

17

Я создаю сервис поверх хранилища данных Google App Engine, которое в конечном итоге становится единым хранилищем данных. Для моего приложения это нормально.

Тем не менее, я разрабатываю тесты, которые делают такие вещи, как объект PUT, а затем объект GET и проверяют свойства возвращаемого объекта. К сожалению, поскольку хранилище данных в конечном итоге непротиворечиво, эти простые тесты не воспроизводимы.

Как вы тестируете в конечном итоге непротиворечивый сервис?

Дуг Ричардсон
источник
2
Почему вы тестируете в первую очередь ожидаемую воспроизводимость по отношению к внешнему сервису?
... а что вы на самом деле пытаетесь проверить? ваш код? или гугл?
5
Я тестирую всю систему. То есть это интеграционные тесты, а не юнит-тесты.
Даг Ричардсон
3
How can I reproducibly test an eventually consistent service? - Ты не можешь. Вы должны удалить слово «воспроизводимо» или слово «в конце концов»; Вы не можете иметь оба.
Роберт Харви
1
Если это в конечном итоге непротиворечиво, независимо от того, воспроизводимо это или нет, любой результат будет успешным. Вы уже сказали, что это хорошо для вашего приложения, так что вы на самом деле тестируете? Возможность? Интеграция с GAE? Ваш код?
Laiv

Ответы:

16

При разработке функциональных тестов учитывайте нефункциональные требования - если у вашей службы есть нефункциональное требование «Согласовано в течение x (секунд / минут / и т. Д.)», Просто запустите запросы PUT, подождите x, затем запустите запросы GET.

В этот момент, если данные еще не «поступили», вы можете считать, что запрос PUT не соответствует вашим требованиям.

Дэн Амброджио
источник
7

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

Создайте поддельный сервис, который обрабатывает запросы PUT и GET, но имеет дополнительную операцию для обеспечения его согласованности. Ваш тест тогда:

datastore.do_put(myobj);
datastore.make_consistent();
validate(datastore.do_get(), myobj);

Это позволяет вам проверить поведение вашего программного обеспечения, когда GET успешно получает объект PUT. Это также позволяет вам протестировать поведение вашего программного обеспечения, когда GET не находит объект (или правильный объект) из-за того, что сервис еще не согласован. Просто пропустите звонок make_consistent().

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

  1. обеспечить соответствие показателей среднего и наихудшего времени между PUT и последующим GET; и
  2. убедитесь, что ваш поддельный сервис ведет себя так же, как реальный сервис. Смотрите https://codewithoutrules.com/2016/07/31/verified-fakes/
Джонатан Джидди
источник
6

Итак. «Что ты тестируешь» - ключевой вопрос.

  • Я проверяю свою внутреннюю логику того, что происходит, предполагая, что Google работает

В этом случае вы должны издеваться над сервисами Google и всегда возвращать ответ.

  • Я тестирую свою логику, чтобы справиться с временными ошибками, которые, как я знаю, будет производить Google

В этом случае вы должны издеваться над сервисами Google и всегда возвращать временную ошибку до правильного ответа

  • Я проверяю, что мой продукт будет работать с реальным сервисом Google

Вы должны ввести настоящие сервисы Google и запустить тест. Но! Код, который вы тестируете, должен иметь встроенную обработку повторных ошибок (повтор). Так что вы должны получить последовательный ответ. (если Google очень плохо себя ведет)

Ewan
источник
+1 за предложение Mock - я бы отдал больше голосов за дополнительные опции, если бы мог.
Маккотл
6

Используйте одно из следующего:

  • После PUT повторите GET N раз до успеха. Сбой, если нет успеха после N попыток.
  • Спи между PUT и GET

К сожалению, вы должны выбрать магические значения (N или продолжительность сна) для обеих этих техник.

Дуг Ричардсон
источник
1
Не могли бы вы уточнить: это альтернативы или дополнительные? Я думаю, вы хотите сказать, что они являются альтернативами - и я так о них думаю. Но, возможно, я ошибаюсь.
Робин Грин
1
Правильно, я имел в виду их альтернативы.
Даг Ричардсон
2

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

Компромисс в том, что строго согласованные запросы довольно сильно ограничены по скорости (что-то, с чем вы можете жить во время тестирования).

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

Например, вы можете вызывать методы start_debug_strong_consistency()и end_debug_strong_consistency().

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

Единственное изменение в реальных запросах, которые вы тестируете, - это вызов, setAncestor(your_debug_key)если этот ключ существует.


источник
1

Один подход, который хорош в теории, но не всегда может быть практичным, состоит в том, чтобы сделать все операции записи в тестируемой системе идемпотентными . Это означает, что, предполагая, что ваш тестовый код тестирует все в фиксированном последовательном порядке, вы можете повторить все чтения и все записи по отдельности, пока не получите ожидаемый результат, повторяя попытки до истечения некоторого времени ожидания, которое вы определяете в тестовом коде. То есть выполните операцию А1, повторяя, если необходимо, до тех пор, пока результат не станет В1, затем выполните операцию А2, повторяя попытку, если необходимо, до результата В2, и так далее.

Тогда вам не нужно беспокоиться о проверке предварительных условий операций записи, потому что операции записи уже будут проверять их для вас, и вы просто повторяете их, пока они не завершатся успешно!

Максимально используйте те же тайм-ауты «по умолчанию», которые можно увеличить, если вся система работает медленнее, и индивидуально переопределить значения по умолчанию при повторных попытках особенно медленных операций.

Робин Грин
источник
1

Сервис, такой как хранилище данных Google App Engine, основан на репликации данных в нескольких точках присутствия (POP). Любой интеграционный тест для в конечном итоге непротиворечивой службы на самом деле является тестом скорости репликации этой службы в его наборе POP. Скорость, с которой контент распространяется на каждую POP в данной услуге, не будет одинаковой для каждой POP в пределах службы, в зависимости от ряда факторов, таких как метод репликации и различные проблемы Интернет-транспорта - это два примера которые составляют большинство отчетов в любой в конечном итоге непротиворечивой службе хранилища данных (по крайней мере, таков был мой опыт, пока я работал в крупной CDN).

Чтобы эффективно протестировать репликацию объекта на данной платформе, вам нужно настроить тест так, чтобы он запрашивал один и тот же недавно размещенный объект от конкретно каждой из POPs службы. Я предлагаю проверять список POP от одного до пяти раз или до тех пор, пока все отчеты POP в вашем списке POP не будут содержать объект. Вот набор интервалов для выполнения теста, который вы можете настроить: 1, 5, 60 минут, 12 часов, 25 часов после помещения его в хранилище данных. Ключом является регистрация результатов в каждом интервале для последующего просмотра и анализа, чтобы почувствовать способность данного сервиса глобально реплицировать объекты. Часто службы хранилища данных извлекают локальную копию в POP только после того, как она была запрошена локально [маршрутизация выполняется по протоколу BGP, поэтому ваш тест должен запросить объект из каждой конкретной POP, чтобы он был глобально действительным для данной платформы] , В случае хранилища данных Google вам нужно будет настроить тест для запроса данного объекта из «более 70 точек присутствия в 33 странах»; вам, скорее всего, придется получить список URL адресов, специфичных для POP, из службы поддержки Google [ref:https://cloud.google.com/about/locations/ ] или, если Google использует Fastly для репликации, Fastly Support [ https://www.fastly.com/resources ].

Несколько преимуществ этого метода: 1) Вы почувствуете платформу репликации данного сервиса, узнаете его сильные и слабые стороны в целом в глобальном масштабе [как это было во время интеграционного теста]. 2) Для любого объекта, который вы тестируете, у вас будет доступный инструмент для подогрева контента [сделайте первый запрос, который создаст копию в заданном локальном POP] - таким образом, вы обеспечите способ распространения контента по всему миру, прежде чем ваши клиенты запросят его у где-нибудь на земле.

Рами Куттаине
источник
0

У меня есть опыт работы с Google App Engine Datastore. Удивительно, что он работает локально и часто более «в конечном итоге», чем «последовательный». Простейший пример: создать новую сущность, а затем извлечь ее. За последние 5 лет я часто видел, как локально работающий SDK не находил новую сущность сразу, а находил ее примерно через полсекунды.

Однако, работая с реальными серверами Google, я не видел такого поведения. Они стараются, чтобы ваш клиент Datastore всегда работал на одном и том же сервере, поэтому обычно любые изменения немедленно отражаются в запросах.

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

christian.simms
источник
Хотя это удобно, это может привести к тому, что в ваших интеграционных тестах будут обнаружены незначительные поломки с участием нескольких серверов приложений. Я думаю, что они сделали локальный сервер в конечном итоге согласованным по уважительной причине!
Робин Грин