Файл: Тестирование производительности программ: подходы в зависимости от категорий приложений.pdf

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 29.03.2023

Просмотров: 394

Скачиваний: 10

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

СОДЕРЖАНИЕ

ВВЕДЕНИЕ

1. ОСНОВНЫЕ АСПЕКТЫ ТЕСТИРОВАНИЯ И ОТЛАДКИ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ

1.1 Понятия тестирования и отладки программного обеспечения

1.2 Цели и задачи тестирования программного обеспечения

1.3 Этапы тестирования программного обеспечения

1.4 Комплексное тестирование программного обеспечения

1.5 Восходящее и нисходящее тестирование

2. РАЗЛИЧНЫЕ ПОДХОДЫ К ТЕСТИРОВАНИЮ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ

2.1 Метод сандвича

2.2 Метод «белого ящика»

2.3 Методы тестирования на основе стратегии «белого ящика»

2.4 Метод «черного ящика»

2.5 Методы тестирования на основе стратегии «черного ящика»

3. ТЕСТИРОВАНИЕ ПРОИЗВОДИТЕЛЬНОСТИ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ

3.1 Виды тестирования производительности

3.2 Основные тесты производительности

3.3 Примеры тестирования на производительность

3.4 Способы повышения производительности

ЗАКЛЮЧЕНИЕ

СПИСОК ИСПОЛЬЗОВАННОЙ ЛИТЕРАТУРЫ

  1. Неправильный. Пытаться сделать больше работы, чем система способна выполнить. Это приведет лишь к неконтролируемому снижению производительности (из-за swopping’а памяти, перегрузки CPU/HDD/IO, и т.д.)
  2. Неправильный. Все остальное. Повредить данные, выдавать пользователю мусор вместо требуемого HTML/XML, не выдавать никакого ответа и так далее.

Рис. 4.

Типичный график нагрузочного тестирования. Система способна обработать не больше 100 запросов в секунду. Запихивание в систему большего объема работы (300 запросов) только создает длинную очередь из 200 ждущих запросов.

Рис. 5. График тестирования

Тестирование, естественно, должно быть автоматизированным (как иначе имитировать работу сотен пользователей). Есть хороший выбор инструментов тестирования (Jmeter, Grinder, Load Runner).

Тестирование должно быть повторяемым (любой тест можно воспроизвести и сравнить производительность приложения до оптимизаций и после).

Любой участник команды (разработчик или тестировщик) должен иметь возможность провести тест на выбранном стенде в DEV или QA или на собственном компьютере, чтобы провести эксперимент или ревью:

  • Тестовые скрипты, их настройки, тестовые данные, скрипты генерации тестовых данных - в Version Control System
  • конфигурационные файлы приложения, OC и middleware (MongoDB, Elastic Search/ и т.д.) в Version Control System.
  • Инструмент тестирования - любой разработчик может легко скачать и установить его на свой компьютер (нужны лицензии или инструмент должен быть open source).
  • Отчет о тестировании должен показывать:
  • Производительность по каждой тестируемой задаче («полнотекстовый поиск», «просмотр накладной» и т.д.) по результатам последнего теста.
  • Тренд в разрезе задача/производительность/количество пользователей за период, чтобы отследить, как недавние изменения в продукте повлияли на производительность

Необходимо отдельно тестировать производительность в режиме «кеширование включено» и «кеширование выключено». Иначе мы можем получить завышенные результаты. Кеширование - может сильно повысить производительность и ,поэтому, используется, и в нашем продукте и во всех лежащих в его фундаменте Middleware (Веб сервер, СУБД,и т.д.). Но, в зависимости от того, как пользователи используют наш продукт, кеширование может дать как значительный выигрыш, так и нулевой и даже отрицательный (т.е., снизить производительность). Например, функция веб сайта «пользователь просматривает свой приватный профиль» не выигрывает от использования кеширования:


  • пользователь не будет просматривать свой профиль несколько раз подряд,
  • другим пользователям доступ к этим данным запрещен,
  • в целом этой функцией редко пользуются.

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

В ходе тестировании желательно фиксировать показатели нагрузки на железо/middleware. Это может помочь найти узкие места (далее bottleneck) в системе.

3.4 Способы повышения производительности

Чтобы получить высокую производительность:

  • Уменьшайте длину задач (т.е. Latency)
  • Но не за счет излишнего увеличения их «ширины». Например, слишком агрессивное кеширование памяти в задаче «сгенерировать веб страницу» ускорит ее выполнение, но заодно увеличит расход памяти на каждого пользователя - и ,в результате, сократит Bandwitch
  • Уменьшайте ширину задач в конвейере
  • Уменьшайте количество задач в конвейере

Есть 3 способа достичь этого:

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

Основные способы ускорения выполнения задачи:

  • Кеширование
  • Предварительная калькуляция
  • Предварительная инициализация
  • Пакетные операции

Кеширование

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

Пример:

Прочитали данные с диска - запомнили в оперативной памяти. Сэкономим в следующий раз на ожидании дискового ввода-вывода.

Не имеет смысла кешировать данные, которые не будут повторно востребованы. Такое кеширование только понизит производительность - из за накладных расходов на помещение данных в кеш/поиск в кеше.

Пример: Кеширует ли Google результаты поиска (вот этот набор из 100 000 ссылок по поисковой фразе «database performance»)? Предположим, что если и кеширует, то на короткий промежуток времени. Вероятность того, что много разных людей будут искать одно и то же - невелика. Вероятность, что они сформулируют свой поисковый запрос одинаково, слово в слово – еще меньше.


Решение принимается по каждому виду данных/бизнес сущности отдельно. Ищется баланс с учетом размера данных, стоимости их вычисления/чтения с HDD, вероятности их повторной востребованности, размера, который они занимают в кеше/частоты изменения данных/того, насколько «болезненно» будет для пользователя получение «устаревших» данных из кеша.

Если отмерить для кеша слишком мало памяти – не хватит места для данных, слишком много – будет тратиться больше времени на поиск в нем. Ищем баланс, используем статистику использования кеша.

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

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

Говоря об имплементации, потребуется

  • спецификация для имен ключей в кеше;
  • спецификация формата данных в кеше;
  • спецификация зависимостей («когда чистите в кеше Пользователя, вычистите и его Профиль»)

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

Базы данных используют кеширование двояко:

  1. Низкоуровневый кеш блоков данных
  2. Кеш запросов (где ключом является «SELECT FistName,LastName from Users Where ID=123», а значением - прочитанный набор записей)

Кеш запросов хорошо помогает при READ-ONLY доступе к небольшому объему данных(чтобы полностью уместился в кеше). Следует понимать, это не замена полноценному кешированию на уровне приложения.

Дело в том, что СУБД «не знает» бизнес-логики приложения и оперирует только текстами SQL запросов. Это создает проблемы c дубликацией/запихиванием слишком большого объема данных в кеш/излишне агрессивном сбросе кеша.

Например, база данных получила SQL два запроса подряд:

  1. SELECT * from user_profile where ID=123;
  2. UPDATE user_profile SET compensation_coeffcient=1.5 WHERE employee_grade=4;

Устарели ли результаты «SELECT …»? С точки зрения приложения - нет, потому что поле compensation_coeffcient им не используется, либо пользователь с ID==123 не имеет «employee_grade==4». Но СУБД либо не может этого знать либо не может эффективно отследить.

Поэтому явный контроль над кешированием на уровне приложения дает наибольший выигрыш в производительности.

Когда стоимость вычисления данных велика, вероятность повторного обращения к тем же данным значительна и частота изменений данных мала - имеет смысл предварительно вычислить их и сохранить в persistent storage.


Пример:

Чтение из СУБД неких редко изменяемых, но часто требуемых данных выполняется медленно из-за сложных SQL запросов со множеством JOIN’ов. Изменить структуру СУБД мы не хотим(другие бизнес-процессы требуют именно такой структуры).

Решение - Выполнять чтение и преобразование данных фоновым процессом, который сохраняет уже обработанные данные в «кеширующей таблице» в СУБД.

Объединение задач в «пакет» (batch) позволяет экономить на накладных расходах.

Типичное серверное приложение извлекает, по запросу пользователя, данные из СУБД(Сервера поиска/внешней системы/и т.д.)

Установка нового соединения съедает время (network latency/инициализация нового потока на стороне СУБД). Пул заранее открытых соединений к серверу СУБД решает эту проблему. С другой стороны, больше открытых соединений - больший расход ресурсов.

Без пула соединений, цикл прост:

  1. Приложение получило задачу на выполнение
  2. Открыло соединение (новое, «чистое»)
  3. Послало запрос
  4. Получило ответ
  5. Закрыло соединение (если в приложении до этого произошел сбой, СУБД сама закроет, когда сработает timeout )

Типовые проблемы при использовании пула соединений:

  • Шаг №2 может надолго заблокировать поток. Например, приложение содержит ошибку и не всегда возвращает соединение в пул. В результате все незанятые соединения в пуле оказались исчерпаны. И реализация метода «openConnection» в пуле ждет, когда освободится одно из занятых сейчас соединений, пока не сработает таймаут(или вечно...)
  • Полученное на шаге №2 соединение уже использовалось ранее и могло «унаследовать» от предыдущего потока целую россыпь проблем (НЕзакрытая транзакция/НЕснятые блокировки/мусорные настройки Transaction Isoloation Level|Charset conversion/мусор в server side SQL variables).
  • Соединение может быть уже закрыто сервером СУБД из-за ошибок, либо длительной неактивности.
  • Полученный на шаге №4 ответ может быть вовсе не ответом на посланный в №3 запрос. Это может быть ответ на запрос, посланный предыдущим «пользователем» соединения.

Поэтому использование persistent соединений требуют поддержки на уровне реализации пула/протокола/драйвера БД/СУБД.

  • Сброс состояния соединение при возвращении в пул - СУБД должно предоставлять такую возможность и драйвер в приложении должен ею пользоваться.
  • Идентификация на уровне драйвера/протокола - к какому посланному ранее запросу относится полученный драйвером от СУБД ответ.
  • Отслеживание и четкое разграничение драйвером ошибок на сетевом уровне(timeout/connection closed) от ошибок уровня приложения (SQL query has error). Первые делают невозможным использование соединения/требуют выбросить его из пула. Вторые позволяют продолжить его использование.
  • Необходим лимит на количество в пуле (не больше, чем сервер приложений может обработать параллельно).
  • Лимит на начальное количество соединений в пуле.
  • Настройка - Сколько добавлять в пул за раз, когда не хватает.
  • Timeout на извлечение из пула. Добавление нового соединения может занять от доли секунды до часов(перегруженный сервер СУБД/сбой или неправильная настройка в файерволе или load balancer’е). Последнее, чего мы хотим - толпа потоков в сервере приложений, заблокированных навечно, потому что в пуле закончились соединения и он заблокировался при добавлении нового.

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

Например, у нас есть сервер - калькулятор. Пользователи посылают ему арифметические выражения, например «2+2» и сервер вычисляет результат – в нашем примере «4». Аппаратное обеспечение - 1 (один) CPU, с одним ядром. Сколько пользователей сервер может обслужить параллельно? Ответ – только одного! Потому что используемый ресурс - только CPU и RAM.

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

Представьте, что к серверу обратились 60 пользователей одновременно.

Сколько времени нужно, чтобы их обслужить используя один поток? Одна секунда * 60 пользователей = одна минута. Одного пользователя сервер обслужит за 1 секунду, второму придется подождать 2, самому невезучему – 60. Хотя бы 30 пользователей будут обслужены быстрее чем за 30 секунд.

Если мы распараллелим работу внутри сервера между 60 потоками, каждый обслуживает один запрос, все запроcы обрабатываются строго параллельно – сколько времени пользователи будут ждать ответа? 60 секунд. Причем, все пользователи будут ждать по 60 секунд. Многопоточность/параллелизм только ухудшают производительность, когда мы пытаемся запихнуть в систему больше работы, чем она способна выполнить.

Другой пример – WEB сервер, раздающий HTML/JS/CSS файлы. Аппаратное обеспечение – 4 ядра CPU. Сколько пользователей сервер может обслужить параллельно? Намного больше 4-х, потому используемые ресурсы - не столько CPU/RAM, сколько Disk I/O. Серверу следует создать намного больше 4-x потоков – они все равно будут ждать, когда завершится чтение с диска. Если потоков будет слишком мало – тогда уже их количество станет «бутылочным горлышком».

ЗАКЛЮЧЕНИЕ

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

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