Upload
highload2009
View
1.326
Download
2
Embed Size (px)
Citation preview
Тестирование в компании
Дмитрий Зенович
Начальные условияДесятки типов онлайн демонов на C++
Десятки offline скриптов на PHP и демонов на C++
веб-интерфейс
API
генераторы статистики и отчетов
Объем кода – несколько сотен тысяч строк
Начальные условияЧто хотелось сделать:
• сократить время итерации и увеличить продуктивность разработки
• создать отдел тестирования (тестирование проводилось силами разработчиков)
• увеличить полноту документации
• оптимизировать процесс внедрения в PHP-разработке
Часть 1
Тестирование
Начало тестированияСтавка на автоматизированное тестирование.
Выбор в качестве фреймворка для автоматизированного тестирования PHPUnitСтандарты кодирования. Один класс в файле. Мэппинг имени класса в имя файла.
Непрерывный рефакторинг. Переписывать все непонятное, упрощать все сложное, избавляться от дублирования.
Библиотека / ConfigurableObjectTestlib_ConfigurableObject Эмуляция массива (доступ по индексу для свойств) Свойства типизированы
/** * HTTP-method * @var string */public $method = 'GET';
Конструктор принимает на вход массив со значениями свойств
Библиотека / ConfigurableObjectЗащищенные свойства /** * @property * @var string */ protected $readOnly; protected function _setReadOnly($value) {throw …} protected function _getReadOnly() { return 1; }
Библиотека / ConfigurableObjectСериализация: toArray(), __toString()Сериализация в XML@serializeXmlAs node(имя)|attribute|text|cdata|none @serializeXmlAs node(имя) в заголовке класса
$obj = Testlib_ConfigurableObject::fromXml('XmlObj', $xmlString);$obj->toXml() ;
Другие виды сериализацииВнутренние форматы сериализации, JSON, HTML, …
Библиотека / ConfigurableObject/** * @serializeXmlAs node(begun) */class Testlib_Client_Daemon_Response_Banner_Body_Begun extends Testlib_ConfigurableObject { /** * @var Testlib_Client_Daemon_Response_Banner_Body_Begun_Banner[] */ public $banner = array();}$obj = Testlib_ConfigurableObject::fromXml( 'Testlib_Client_Daemon_Response_Banner_Body_Begun', $data);
Библиотека / MethodMockTestlib_MethodMockИзоляция тестируемого функционалаПредотвращение лишних действийПредотвращение долгих операций в тестах (БД и т.п.)Упрощение подготовки начальной конфигурации
PECL-модуль runkit (runkit_method_copy, runkit_method_redefine, runkit_method_remove, …)Перехват встроенных функций PHPПатчи runkit
Библиотека / MethodMock$m1 = Testlib_MethodMock::interceptMethodByCode("MyClass", "myMethod", "echo '1';");$m2 = Testlib_MethodMock::mockFunctionResult('getValue', 12345, array(2,1));$m3 = Testlib_MethodMock::interceptMethodByCode("MyClass", "myMethod", "echo 'called';");$m1->getCalledArgs();$m2->getCalledResults();$m3->isCalled();$m1->countCalled();$m2->resetCallStack();$m3->revert();
Библиотека / TcpdumpCatcherTestlib_TcpdumpCatcher$catcher = new Testlib_TcpdumpCatcher(TESTLAB_HOST, TESTLAB_USER, TESTLAB_PASS, "192.168.1.3", "8080", "eth0");
// ...
$dump = $catcher->finish();
Внутри: tcpdump, анализ собранных пакетов, склеивание с учетом фрагментирования, парсинг
Работает локально или удаленно
Библиотека / LogCatcherTestlib_LogCatcher$catcher = new Testlib_LogCatcher("my.log", /*"192.168.1.3", "myuser", "123456“*/);
// ...
$log = $catcher->finish();
Внутри: wc -l , tail -n
Работает локально или удаленно
Работа с базой данныхMySQL, транзакционность InnoDB, реализация вложенных транзакций
Testlib_TestDb• Zend_Db • реализация вложенных транзакций• фабрика на основе конфигураций• остановка/восстановление репликации
Очистка по расписанию
Библиотека / DatabaseMockTestlib_DatabaseMock$dbMock = new Testlib_DatabaseMock('Cards', 'CardsForTest', false, array('CatalogTree', 'CatalogInfo')); // ...$dbMock->revert();
Внутри:Testlib_MethodMock::interceptFunction('mysql_query');runkit_constant_redefine(…)
Различные способы экранирования имен таблиц и баз учитываются
Библиотека / DbiCleanerTestlib_DbiCleaner$id = … Testlib_DbiCleaner::add('DbName.tableName', 'id', $id);// ... Testlib_DbiCleaner::start();
Есть также очистка из командной строки
Библиотека / FakeDaemonTestlib_FakeDaemonПодмена ответов. nginx + php. Настройка ini-файла:[:7099]serviceName="4test"port=80host="lab128"logfile="./7099-4tests.log"
Подмена ответа:Testlib_FakeDaemon::setCustomResponse('4test',$requestText, $responseText);
Библиотека / FakeDaemon$responseText может не содержать HTTP-заголовков$requestText может быть неполным текстом запроса
Внутри подмены:Testlib_FakeDeamon_CustomResponse::fromRequestAndResponse()Создаются условия по requestMethod, handler, каждому get-параметру, по каждому заголовку, кроме «Host». Порядок полей не важен.Ответ и его условия сохраняются в базу и заносятся в DbiCleaner
Библиотека / FakeDaemonОбработка запроса• Определение конфигурации по порту• Получение всех подмененных запросов для сервиса (в обратном порядке)• Для каждой подмены проверяются условия (И, urldecode для get-параметров)• Если подмена найдена, то возвращается она, иначе ответ ищется в кэш, если там нет, то берется ответ настоящего демона.
Позволяет тестировать программы независимо!Тест должен очищать после себя подмены.
Создание клиента daemon’аБазовый класс (Testlib_ClientTest)Работа с конфигом демона в БД (получение, изменение)Абстрактный http-запрос к демонуАбстрактный http-запрос к демону для тестов (конфиг, лог, подмена ответов других демонов, TcpdumpCatcher, контроль изоляции демона, поиск ошибок в логе)Запуск и остановка демона, запущен ли демон, ожидание запускаrequestHandler, requestHandlerForTests (Testlib_ConfigurableObject)
Создание клиента daemon’аНовый клиентСоздаем новый класс; прописываем в свойствах имя демона, таблицу с конфигом, «нижние» демона; реализуем методы для каждого хэндлера:/** * @param mixed<Testlib_Client_Daemon_RequestParamsForTests_Index> $params * @return Testlib_Client_Daemon_ResponseForTests_Index */ public static function requestIndexForTests($params = array()) { return self::requestHandlerForTests('index', $params); }создаем классы (Testlib_ConfigurableObject) для входных и выходных данных хэндлеров
Создание клиента daemon’а Написание тестов демонов $params = array( 'params' => array( 'getParams' => array(...), ... ), 'tcpdump' => array(...), 'customResponses' => array( Testlib_FakeDaemon_CustomResponse::fromRequestAndResponse( 'subdaemon', '/handler', (string) (string) new Testlib_Client_Subdaemon_Response_Handler_Body(...) ), ... )); $result = Testlib_Client_Daemon::requestIndexForTests($params);
БиблиотекаБазовые классы тестов и сьютов демонов
Testlib_TestSuite остановка и возобновление репликацииprepare/cleanup
CommonSuite (много тестов для одного запроса к демону)
Тестирование PHPМожно использовать библиотеки PHP-разработчиковМетоды белого ящика
Написание юнит-тестов PHPБазовый клаcс Testlib_TestCase_Php Блокирование коммита БД Cтарт транзакции в setUp, откат в tearDown Очистка глобальных объектов Список sql-запросов в отчетах об ошибке Отслеживание коммитов и роллбэков Проблема parent::setUp и parent::tearDown
Тестирование PHPTestlib_ScriptRunnerВозвращать результат, вывод, log скриптаУмеет дожидаться освобождения lock-файлаrunScript()runScriptByIncluding() удаляет константы, определенные в скрипте удаляет функции, определенные в скрипте неправильное окружение, зато есть возможность подменять методы, базы, откатывать транзакцию
Тестирование web-интерфейсаДоработки PHPUnit в части работы с SeleniumБолее подробный отчет об ошибкеДобавлены:URL страницы, скриншот, HTML-source, история команд
Selenium, лог запросов БД, отчет об ошибке в формате HTML
Переписан драйвер работы с Selenium-RCИсправление HTTP (Ускорение > 10 раз)Перекодировка запросов и ответовОтмена автоматического закрытия окна браузера при ошибках
Тестирование web-интерфейсаОбщий подход: Testlib_TestCase_Selenium, ускорение авторизацииИспользовать Selenium как можно меньше!
Удаленный перехват методов и функций PHPОболочка MethodMock для удаленного перехвата.
Принцип работы:sessionId, Zend_Cache
Тестирование JavaScriptTestlib_TestCase_JsBlock
Подмена в браузеренастраиваемый прокси для подмены ответов демонов и статических
файловвеб-нитерфейс для управлениями правилами
Виртуальные стендыОднопоточность тестов• Переменные состояния программ• Общие объекты базы данных (один тест меняет конфигурацию других)• Общие динамические файлы на файловой системе• Lock wait-timeout базы• TcpdumpCatcher• lock- и log-файлы• DbiCleaner• FakeDaemon (чужие ответы)
Замедление тестов из-за сети, сложность поддержки распределенной с-мы.
Виртуальные стендыДля каждого потока тестов должны быть свои:• тестируемая программа• база данных• динамические каталоги• сетевые порты/хосты для хождения к другим программам по
сети• каталоги с log- и lock-файлами
Желательно, чтобы все это было на одной машинеПереносим все в тестовый стенд!
Виртуальные стендыВиртуальный компьютер на основе xenСоздание нового стенда:php /begun/www/teststand/createLab.php --daemon=<daemon>
[--start] [--swap]Для каждой программы своя конфигурация (по <daemon>):• Базы данных и таблицы (с данными и без)• Скрипт для установки тестируемой программы и зависимостей• Скрипт для запуска тестируемой программы и зависимостейПри создании стенда также делается checkout тестов, тестовых
библиотек и утилит
Виртуальные стендыСписок виртуальных машин:xm list
Остановка/восстановление:xm start/destroy
Удаление:php /begun/www/teststand/deleteLab.php
Важно: база данных должна содержать только минимальный набор данных, необходимых для работы программы и тестов.
Непрерывная интеграцияphpUnderControl. Стадии билда:• Создаем новый стенд• Компилируем тестируемую программу (C++)• Устанавливаем скомпилированную программу на стенд (C++)• Обновляем/выкачиваем код программы (PHP)• Обновляем/выкачиваем тесты и тестовые библиотеки• Прогоняем тесты• Удаляем тестовый стендБилды запускаются автоматически (интервал
времени/модификация кода)Отчет формируется автоматически
Непрерывная интеграцияЧто запускать непрерывно:• тесты стабильной ветки, которые не прогоняются перед
выкладкой• тесты которые запускаются редко (текущая ветка и т.п.)• все «маленькое», что очень лень запускать вручную
Бонусы:• степень покрытия кода тестами• возможность автоматической генерации документации• выявление проблем кода (стандарты кодирования, кол-во
методов в классе, кол-во строк в методе, сложность кода, …)
Конец первой части
Часть 2
Нагрузочное тестирование
Нагрузочное тестированиеМинусы тестовой среды: - Проблематичность создания адекватной нагрузки - Трудности в повторении среды production (железо, сеть, …) - Необходимость в интегральном подходе (связанные демона и
базы) - Необходимость в системах мониторинга - Необходимость в специалистах по нагрузочному тестированию
После функционального тестирования задача передается в нагрузочное тестирование.
Нагрузочное тестированиеИнструмент для дублирования трафика• Безопасно дублирует production запросы (%)• Динамическое включение, выключение, перенацеливание• Гибкое управление долей дублированного трафика (от 0% до
N*100%)• Настраивается через web-интерфейс
Нагрузочное тестированиеТестирование новых версийВыведение машины из productionУстановка на машину новой версииДублирование трафика с production-машины (%)Мониторинг нагрузки на подсистемы самой машины (диск, CPU,
сеть) и времени ответаМониторинг логов тестируемого демонаОценивается динамика нагрузки на «нижние» подсистемы
Нагрузочное тестированиеСтресс-тестирование старых версийПостепенное увеличение нагрузки (>100%)Мониторинг кол-ва ошибочных ответов и времени ответаМониторинг нагрузки на подсистемы самой машины (диск, CPU,
сеть) и времени ответаМониторинг логов тестируемого демонаМониторинг нагрузки на failover-машинуИзучение логов балансера (время ответа)
Комплексная оценка производительности
Конец второй части
Часть 3
Эпилог
Тестирование улучшает климат• код на production точно совпадает с кодом в стабильной ветке• утверждены стандарты кодирования (PHP)• новый/измененный код покрывается юнит-тестами (PHP)• введена процедура code-review• перед коммитом в стабильную ветку прогоняются тесты• подробные описания проектов в wiki• подробные описания задач в тикетах• введены обязательные декомпозиции разработки и
тестирования с оценкой трудозатрат• месячные планы
Результаты и будущее Подробная документацияВнедрение проверенного кодаСтабильная и предсказуемая разработкаВыполнение месячных планов
Сбор и анализ формализованных отчетов об ошибке
Ссылки + литератураPHP: http://php.net/PHPUnit: http://phpunit.de/ phpUnderControl: http://phpundercontrol.org/Selenium: http://seleniumhq.org/runkit: http://pecl.php.net/package/runkit
Extending and Embedding PHP by Sara Golemon
Мессарош, Дж. Шаблоны тестирования xUnit: рефакторинг кода тестов(XUnit Test Patterns: Refactoring Test Code)
Дастин, Э. Автоматизированное тестирование программного обеспечения: пер. с англ. / Э. Дастин, Дж. Рэшка, Дж. Пол