Мода – заразная штука и в то же время противоречивая. Купальник из весенней коллекции 2013 года, подходящий для Евы Мендес, может не подойти Монике Беллуччи. Ну вот не идет, и все. Наверное, Ева и Моника никогда не прочтут эти строки, поэтому речь сегодня пойдет не о купальниках, а о моде на методики/инструменты тестирования и выживании в этой среде.
Подходы и утилиты, хорошо зарекомендовавшие себя в одном проекте, могут с треском провалиться в другом. Когда апологеты очередного инструмента или методики говорят о том, что их детище хорошо зарекомендовало себя в 99% проектов, то так и хочется спросить: в 99% известных вам проектов или же в 99% проектов, где внедрялась эта методика? И что, все остальные проекты с другими подходами обречены? Ну тогда это не просто методика, а настоящая серебряная пуля. Хотя нет, это самый настоящий золотой молоток. Как правило, самые ярые сторонники того или иного инструмента не в полной мере учитывают суть того или иного продукта, его назначение, интерфейсы, backend/frontend, навыки людей в проекте, наличие времени, ситуацию на рынке и многое другое. Прогресс – это здорово. Новая методика может прийтись ко двору, и после ее внедрения проект начинает лучше дышать. Но бывает и так, что все идет наперекосяк, крокодил не ловится, не растет кокос. В угоду новым веяниям и карьерным бенефитам ломаются устоявшиеся практики, зарекомендовавшие себя с лучшей стороны. И перекрывается дорога альтернативным вариантам. Чем дальше в лес, тем больше дров. Процессы (тесты, приоритеты) перестраиваются в угоду тому или иному инструменту и всем его очевидным недостаткам.
Acceptance-тесты на базе FitNesse/Cucumber/Robot Framework – одно из популярных веяний в последние годы. Как правило, они идут рука об руку со сценариями (user stories) на базе системы given-when-then. Тесты должны быть простыми, атомарными, быстрыми и понятными бизнес-людям в такой степени, чтобы они могли вносить в них изменения и интерпретировать результат. Идея сама по себе хорошая. Acceptance-тесты позволяют не только быстро проверить билд, но и служат своего рода документацией к продукту. Однако в таких тестах/инструментах есть минусы. Только… тссс … говорить о них нужно тихо, шепотом, украдкой, потому что для апологетов это не минусы, а вовсе даже плюсы. Вот несколько примеров для FitNesse от Евы Мендес:
1. FitNesse-тесты облекаются в форму таблицы. Порой это действительно удобно. Но иногда это прокрустово ложе, в котором трудно разместить тест. Но тут сразу возражение: “Это у тебя тест плохой. Нужно написать такой тест, чтобы он удобно размещался в таблице”. Конечно, таблица плохой быть не может. Подстроим содержание под форму.
2. Тесты редко бывают простыми настолько, чтобы требовалось сравнить 10 и 15. Тесты – это код. Если размещать его в таблице, то его неудобно там отлаживать. То есть, ты отлаживаешь его не в FitNesse, потом вставляешь его в FitNesse и так по кругу. А еще код в таблице непонятен бизнес-людям. Но тут возразят: “А код можно спрятать в модули и импортировать/инклудить в начале страницы. А еще тесты должны быть настолько простыми, что их не надо отлаживать. Простыми тестами можно полностью покрыть всю функциональность продукта. А если нельзя, то оставшиеся можно покрыть модульными тестами”. Вот так, отбросьте все сомнения.
3. В FitNesse нет возможности организовать циклы for/while и ветвление по if. Зачастую это бывает очень нужно, но тут возразят: “Не нужно, это ты тест неправильно придумал, не нужен такой сложный”.
Пункты 1-3 частично (не в полной мере) можно обойти, если создавать свои fixtures. Так обычно и поступают, когда FitNesse навязан сверху, а функциональности не хватает.
Список слабых мест FitNesse-системы, которые при этом отнюдь не отменяют ее достоинства, на этом не исчерпывается. В битве между сторонниками и противниками acceptance-тестов сломано немало копий, и в качестве примера приведу лишь высказывание Джеймса Шора: “For those who don’t know, I was the coordinator of the Fit project for a while and co-author of the C# version. … My experience with Fit and other agile acceptance testing tools is that they cost more than they’re worth.” Джеймс предлагает альтернативу acceptance-тестам, и его мысли выглядят не менее логичными в сравнении с мнением сторонников acceptance-тестов (к примеру, Гойко – один из них).
Очевидно, у FitNesse есть плюсы и минусы (как у любого другого инструмента). Их нужно учитывать. К примеру, создавать acceptance-тесты для user stories и в то же время уделять внимание более серьезным тестам, которые более глубоко охватывают функциональность продукта. Ниже представлено несколько примеров того как можно интегрировать FitNesse-тесты в общую систему автотестов.
Тесты должны запускаться с выходом каждого нового билда. Нажимать вручную кнопку Test или Suite не хочется. Возможны два других варианта запуска: через REST API и в командной строке:
http://testhost:8080/FrontPage.AutomatedTesting.FlightScenarios.Trailing?suite&format=text java -Xmx1000m -jar C:\Tests\fitnesse.jar -c FrontPage.AutomatedTesting.FlightScenarios.Trailing?suite -p 9200
Если тесты запускаются из внешнего test runner’a (к примеру, Visual Build или просто скрипт на Perl/Python/JScript), то можно управлять порядком запуска тестов, указывая пути к различным сценариям.
Если проект большой и покрытие продукта тестами ведется интенсивно, то в один прекрасный момент их становится слишком много, чтобы запускать их “за раз”. C каждым билдом хочется выполнять группу smoke-тестов (достаточно быстрая проверка основной функциональности), а остальные тесты последовательно перебирать по расписанию вне зависимости от выпуска билда. В этом случае можно предложить два решения:
1) параллельный запуск тестов с нескольких диспетчеров;
2) перебор тестов по расписанию (round robin).
Можно объединить оба этих подхода. К примеру, так:
a) Тесты хранятся в StarTeam (GitHub/TFS/whatever): структура файлов FitNesse, внешние скрипты, вызываемые из FitNesse, ssh-ключи и т.п.;
б) Есть внешний test runner, который запускается на диспетчерах по расписанию с помощью Windows Task Scheduler;
в) В качестве параметра test runner принимает конфигурационный файл. Для каждого диспетчера есть набор конфигурационных файлов, в которых отражена специфика диспетчера и перечень тестов. Например, для диспетчера A есть конфигурационные файлы testrunner.config.A.Monday … testrunner.config.A.Friday, для диспетчера B – testrunner.config.B.Monday … testrunner.config.B.Friday. Пример конфигурационного файла:
# Formats: # Format 0 (Configuration parameters): Param="Value" # Format 1 (Scenarios): Scenarios:Platform1,Platform2,..PlatformN Dispatcher="disp203" AutoUser="Admin2" AutoUserPwd="Fe790ThR" FlightHost="10.30.15.201" Prefix="q203" EmailAddr="ivan.ivanov@company.com" FlightScenarios:Linux, Windows,MacOsx,FreeBsd SupportScenarios:Earth,Sky,Virtual
г) В начале своей работы test runner скачивает тесты из StarTeam и анализирует конфигурационный файл. Во-первых, могут понадобиться изменения в файлах FitNesse, специфичные для диспетчера (например, префикс, используемый при создании объектов, чтобы можно было отличить результаты тестов на разных диспетчерах). Во-вторых, из конфига извлекаются имена пользователей, пароли и подобные вещи, необходимые для запуска тестов на данном диспетчере. В-третьих, конфиг определяет список тестов, которые надо запустить. Test runner читает конфиг и заносит всю информацию в свои структуры данных. К примеру, так:
var Scenarios = []; var ScenariosRegexp = /^\w+:\w+/; var Platforms = []; var ConfigArray = []; var ConfigRegexp = /^\w+=".+"/; var ResultConfig, ResultScenarios var VarName, VarValue; var fso = WScript.CreateObject("Scripting.FileSystemObject"); if (fso.FileExists(ConfigFile)) { var txtStream = fso.OpenTextFile(ConfigFile); while (!txtStream.atEndOfStream) { ConfigString = txtStream.ReadLine(); ResultScenarios = ScenariosRegexp.exec(ConfigString); ResultConfig = ConfigRegexp.exec(ConfigString); if (ResultScenarios) { WScript.Echo("Scenarios matched: " + ConfigString); Scenarios[i] = ConfigString; i++; } else if (ResultConfig) { ConfigArray = ConfigString.split("="); VarName = ConfigArray[0]; VarValue = ConfigArray[1].replace(/"/g,""); this[VarName] = VarValue; WScript.Echo("Configuration variable defined: " + VarName + "=" + VarValue); } else { WScript.Echo("Configuration String not matched: " + ConfigString); } } }
д) По результатам чтения конфига вносятся изменения в FitNesse-файлы (к примеру, в переменные: !define Prefix q203) и запускаются тесты.
В FitNesse есть одна особенность: если все тесты зеленые, но есть exception(s), то страница PageHistory будет желтой. Это хорошо. Сразу видно, что с одной стороны, с тестами все более-менее нормально, а с другой стороны на exceptions нужно обратить внимание. Но бывает так, что exceptions неинформативны и вызваны не столько багами в продукте или автотесте, сколько недоработками в самом FitNesse. С ними можно разбираться и уходить далеко вглубь исходников FitNesse, а можно и просто “забить болт” и проигнорировать, если на помощь opensource-проекту в настоящий момент нет времени и сил. Но вот что трудно проигнорировать, так это то, что тест, окрашенный желтым в PageHistory, будет выглядеть красным в TestHistory (в общем списке результатов всех тестов). Это неудобно тем, что просматривая TestHistory, трудно понять, где тесты действительно провалились, а где отработали, но с exceptions. Плюс к этому, вызывает дискомфорт расхождение в PageHistory и TestHistory.
Иными словами, FitNesse может раскрашивать тест в TestHistory либо красным, либо зеленым. Желтым – не может. Раскраска определяется по имени файла-истории: 20130404235612_331_0_0_1.xml: выделенные числа означают следующее: 311 – количество тестов, 0 – количество failed тестов, 0 – количество ignored тестов, 1 – количество exceptions. Если последние три числа нули, то тест раскрашивается в зеленый цвет. Если не нули, то в красный.
В связи с этим можно анализирировать каждый файл testHistory, и если количество failed-тестов 0 (то есть, все тесты успешны), то количество exceptions тоже устанавливать в ноль.
var FitResultsFolder = FitPath + "\\FitNesseRoot\\files\\testResults"; TuneFitnesseResults(FitResultsFolder); // recursion function TuneFitnesseResults(dir) { // If all tests are green, but there are exceptions, ignore them to mark test green in TestHistory. // For that, 20130401183449_36_0_0_1.xml => 20130401183449_36_0_0_0.xml // Where 36 is the whole number of tests, the first zero stands for wrong tests, the next zero stands for ignored tests // and the last number (1) stands for the number of exceptions var fso, f, fc, fpath; var currentFile, currentFileName, currentFileNameStart, newFileName; var exceptionNumber, ignoreNumber; var FileNameRegexp = /(.+\d+)_0_(\d+)_(\d+)\.xml$/; var FileNameRegexpResult; fso = new ActiveXObject("Scripting.FileSystemObject"); f = fso.GetFolder(dir); fpath = f.Path; //fpath = fpath.replace(/\\/g,"\\\\"); WScript.Echo("Folder: " + fpath); fc = new Enumerator(f.Files); for (; !fc.atEnd(); fc.moveNext()) { currentFile = fc.item(); currentFileName = fpath + currentFile.Name; WScript.Echo("FileName: " + currentFileName); FileNameRegexpResult = FileNameRegexp.exec(currentFileName); if (FileNameRegexpResult) { currentFileNameStart = FileNameRegexpResult[1]; ignoreNumber = FileNameRegexpResult[2]; exceptionNumber = FileNameRegexpResult[3]; if (exceptionNumber != 0) { WScript.Echo("FitNesse Result File with zero wrong tests: " + currentFileName + " ... and it looks like this file has exceptions: " + exceptionNumber); newFileName = currentFileNameStart + "_0_" + ignoreNumber + "_0.xml"; WScript.Echo("New Name for FitNesse Result File: " + newFileName); currentFile.Move(newFileName); } } } var esub = new Enumerator( f.SubFolders ); // Loop through subfolders with a recursive call for(; !esub.atEnd(); esub.moveNext() ) { var fsub = fso.GetFolder( esub.item() ); TuneFitnesseResults( fsub ); } }
Нужно отметить, что у данного подхода есть серьезный недостаток: в TestHistory тест перестает быть красным, но становится не желтым, а зеленым. Это лучше, чем красным, но хочется все же различать в TestHistory красный, желтый и зеленый точно так же как в PageHistory. В связи с этим был написан feature request, и команда, работающая над FitNesse, не оставила его без внимания. Есть надежда, что изменения войдут в ближайший официальный билд, и необходимость в плясках с бубном отпадет.
Если в системе автотестов задействовано несколько машин-диспетчеров, то testResults (<path to Fitnesse>\FitNesseRoot\files\testResults\) и ErrorLogs (<path to Fitnesse>\FitNesseRoot\ErrorLogs\ ) с диспетчеров можно копировать/переносить на агрегатор, где аккумулируются результаты всех тестов. В этом случае, зайдя на http://testresultshub/?testHistory, можно увидеть результаты по всем диспетчерам.
В качестве дополнительного средства можно организовать доставку результатов по E-mail. Например, так:
// Получаем историю запуска тестов var CreateReport = JavaCmd + FitPath + "FrontPage?testHistory>" + ReportFile; WScript.Echo("Report command: " + CreateReport); mshell.Run(CreateReport,1,true); // В истории есть ссылки на pageHistory для каждого теста. // Относительные ссылки ведут на текущий диспетчер, их нужно заменить абсолютными ссылками на агрегатор var mfso = new ActiveXObject("Scripting.FileSystemObject"); var ReportFileHandle = mfso.OpenTextFile(ReportFile); var ReportFileContents = ReportFileHandle.ReadAll(); ReportFileHandle.Close(); var OldHref = /href=\"/g; var NewHref = "href=\"http://" + AgregatorStorage; var ReportFileContentsUpdated = ReportFileContents.replace(OldHref,NewHref); ReportFileHandle = mfso.OpenTextFile(ReportFile,2,true); ReportFileHandle.Write(ReportFileContentsUpdated); ReportFileHandle.Close(); // Отправляем письмо. В теле письма - testHistory, во вложении - логи. var EmailSubject = Dispatcher + ": Autotest results"; var AttachedFiles = "\"" + FitPath + "\\flight.log\"," + "\"" + FitPath + "\\scripts\\testrunner.log\""; var Mailer = "cmd /c " + FitPath + "\\Scripts\\blat.exe " + "\"" + ReportFile + "\"" + " -server relay.company.corp -to " + EmailAddr + " -f results@company.com -subject \"" + EmailSubject + "\"" + " -html -attacht " + AttachedFiles; WScript.Echo("EMail command: " + Mailer); mshell.Run(Mailer,7,true);
Ориентируясь на имя файла-истории, описанное в пункте 4 выше, можно организовать повторный запуск завалившихся автотестов. После завершения всех тестов можно пробежаться по дереву файлов, образующих test history, и выделить тесты, у которых количество завалившихся шагов больше нуля. Из этих тестов формируется конфигурационный файл, описанный в пункте 3 выше, и test runner принимает егов качестве параметра. Мотивы к повторному запуску завалившихся тестов могут быть разные. Какой-то тест может быть нестабильным, где-то была временно недоступна та или иная машина и т.п. Повторный запуск способен расставить все точки над i и предоставить дополнительную информацию для анализа результатов.
Порой не мы раздаем карты, но мы в них играем. И при хорошей игре можно выйти в плюс даже с не самыми лучшими картами.
До встречи.
Что такое качество программного обеспечения и как его улучшить: теория и практика, задачи и решения, подводные камни и обходные пути.
Автор комментария : MyFit, как отличная добавка к Fitnesse | April 25, 2013
Кстати, существенно много доработок к Fitnesse сделано в проекте MyFit, который умеет гораздо более приятно отображать и редактировать тектовые истории, аггрегировать тесты, и копить+анализировать результаты запусков. Подробнее можно найти на сайте проекта (https://code.google.com/p/myfit/) или написать мне.
[Ответить]
Капитан отвечает:
April 25th, 2013 в 5:42 pm
Да, хотелось бы узнать подробности. Есть ли где-нибудь сравнительная таблица достоинств/недостатков MyFit и Fitnesse? Если впридачу к ним будут скриншоты для визуального восприятия, то будет еще лучше.
[Ответить]
Pingback : OpenQuality.ru | Качество программного обеспечения | May 1, 2013
[…] В блоге опубликована статья FitNesse: этюд в БАГровых тонах. […]