Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Go
Code

Тестирование насыщенного данными кода с помощью Go, часть 5

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Testing Data-Intensive Code with Go.
Testing Data-Intensive Code With Go, Part 4

Russian (Pусский) translation by Svetlana (you can also view the original English article)

Обзор

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

Нечеткое тестирование 

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

Когда нечёткое-тестирование полезно?

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

Рассмотрим, например, входные данные в виде глубоко вложенных JSON-документов (очень часто встречающихся в веб-API). Попытка создать полный список тестовых примеров вручную сопряжена с ошибками и требует много работы. Но нечеткое тестирование является идеальной техникой.

Использование нечеткого тестирования

Есть несколько библиотек, которые вы можете использовать для нечеткого тестирования. Мой любимый gofuzz от Google. Вот простой пример, который автоматически генерирует 200 уникальных объектов структуры с несколькими полями, включая вложенную структуру.

Тестирование кэша

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

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

Давайте посмотрим, как проверить поведение кэша гибридного слоя данных Songify.

Удачи и неудачи кеша

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

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

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

Далее я заменим методы getSongByUser_DB () на переменную публичной функции. Теперь в тесте я могу заменить переменную GetSongsByUser_DB () на функцию, которая отслеживает, сколько раз она была вызвана, а затем перенаправляет ее в исходную функцию. Это позволяет нам проверить, извлекал ли вызов GetSongsByUser () песни из кэша или из DB.

Давайте разберем его по частям. Сначала мы получаем слой данных (который также очищает DB и redis), создаем пользователя и добавляем песню. Метод AddSong () также заполняет redis.

Это классная часть. Я сохраняю исходную функцию и определяю новую инструментальную функцию, которая увеличивает локальную переменную callCount (все это в замыкании) и вызывает исходную функцию. Затем я назначаю инструментированную функцию переменной GetSongsByUser_DB. С этого момента каждый вызов гибридного уровня данных для GetSongsByUser_DB () будет переходить к инструментированной функции.

На данный момент мы готовы фактически протестировать операцию кэширования. Сначала тест вызывает метод GetSongsByUser () SongManager, который перенаправляет его на уровень гибридных данных. Кеш должен быть заполнен для этого пользователя, которого мы только что добавили. Таким образом, ожидаемый результат заключается в том, что наша инструментированная функция не будет вызываться, а callCount останется равным нулю.

Последний тестовый пример - убедиться, что если данные пользователя не находятся в кэше, они будут правильно извлечены из DB. Тест завершается путем сброса Redis (очистки всех его данных) и повторного вызова GetSongsByUser (). На этот раз будет вызвана инструментированная функция, и тест проверяет, что callCount равен 1. Наконец, оригинальная функция GetSongsByUser_DB () восстанавливается.

Недействительность кэша

Наш кеш очень простой и не делает никаких действий недействительными. Это работает очень хорошо, если все песни добавляются с помощью метода AddSong (), который заботится об обновлении Redis. Если мы добавим больше операций, таких как удаление песен или удаление пользователей, тогда эти операции должны позаботиться об обновлении Redis соответствующим образом.

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

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

Кэши LRU

Обычно вы не можете просто позволить кешу расти бесконечно. Распространенной схемой хранения наиболее полезных данных в кэше являются кэши LRU (используются реже всего). Самые старые данные извлекаются из кэша, когда они достигают емкости.

Тестирование включает в себя установку емкости на сравнительно небольшое число во время теста, превышение емкости и обеспечение того, чтобы самые старые данные больше не находились в кэше, а для доступа к ним требуется доступ к DB.

Проверка вашей целостности данных

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

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

Проверка ограничений

Ограничения являются основой вашего моделирования данных. Если вы используете реляционную DB, вы можете определить некоторые ограничения на уровне SQL и позволить DB применять их. Нуль, длина текстовых полей, уникальность и 1-N отношения могут быть определены легко. Но SQL не может проверить все ограничения.

Например, в Desongcious существует отношение N-N между пользователями и песнями. Каждая песня должна быть связана хотя бы с одним пользователем. Нет хорошего способа применить это в SQL (ну, вы можете иметь внешний ключ от песни к пользователю и указывать песню одному из пользователей, связанных с ним). Другим ограничением может быть то, что у каждого пользователя может быть не более 500 песен. Опять же, нет способа представить это в SQL. Если вы используете хранилища данных NoSQL, то обычно декларация и проверка ограничений на уровне хранилища данных еще меньше поддерживаются.

Это оставляет вам пару вариантов:

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

Тестирование идемпотентности

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

Например, установка переменной x в 5 является идемпотентной. Вы можете установить x на 5 один раз или миллион раз. Это все еще будет 5. Однако увеличение X на 1 не идемпотентно. Каждое последовательное увеличение меняет свое значение. Идемпотентность является очень желательным свойством в распределенных системах с временными сетевыми разделами и протоколами восстановления, которые повторяют отправку сообщения несколько раз, если нет немедленного ответа.

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

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

Тестирование миграции данных

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

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

Тестирование недостающих данных

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

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

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

Заключение 

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

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

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.