OpenQuality.ru

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

Качество программного обеспечения: в главных ролях

Лента  Радар  Блог  Опыт  
Разум  Видео  Заметки  Эпизоды


FitNesse: этюд в БАГровых тонах

 

1. Вступление

Мода – заразная штука и в то же время противоречивая. Купальник из весенней коллекции 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-тесты в общую систему автотестов.

 

2. Как запускать тесты

Тесты должны запускаться с выходом каждого нового билда. Нажимать вручную кнопку 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), то можно управлять порядком запуска тестов, указывая пути к различным сценариям.

 

3. Как перебирать тестовые сценарии

Если проект большой и покрытие продукта тестами ведется интенсивно, то в один прекрасный момент их становится слишком много, чтобы запускать их “за раз”. 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) и запускаются тесты.

 

4. Заплатка на результаты в FitNesse

В 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, не оставила его без внимания. Есть надежда, что изменения войдут в ближайший официальный билд, и необходимость в плясках с бубном отпадет.

 

5. Где хранить и как отображать результаты тестов

Если в системе автотестов задействовано несколько машин-диспетчеров, то 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);

 

6. Повторный запуск завалившихся тестов

Ориентируясь на имя файла-истории, описанное в пункте 4 выше, можно организовать повторный запуск завалившихся автотестов. После завершения всех тестов можно пробежаться по дереву файлов, образующих test history, и выделить тесты, у которых количество завалившихся шагов больше нуля. Из этих тестов формируется конфигурационный файл, описанный в пункте 3 выше, и test runner принимает егов качестве параметра. Мотивы к повторному запуску завалившихся тестов могут быть разные. Какой-то тест может быть нестабильным, где-то была временно недоступна та или иная машина и т.п. Повторный запуск способен расставить все точки над i и предоставить дополнительную информацию для анализа результатов.

 
Порой не мы раздаем карты, но мы в них играем. И при хорошей игре можно выйти в плюс даже с не самыми лучшими картами.

До встречи.

Отправить в Twitter, Facebook, FriendFeed, ВКонтакте | Опубликовано 08.04.2013 в рубрике "Автоматизация"

Комментарии (3)

  1. Автор комментария : MyFit, как отличная добавка к Fitnesse | April 25, 2013

    Кстати, существенно много доработок к Fitnesse сделано в проекте MyFit, который умеет гораздо более приятно отображать и редактировать тектовые истории, аггрегировать тесты, и копить+анализировать результаты запусков. Подробнее можно найти на сайте проекта (https://code.google.com/p/myfit/) или написать мне.

    [Ответить]

    Капитан отвечает:

    Да, хотелось бы узнать подробности. Есть ли где-нибудь сравнительная таблица достоинств/недостатков MyFit и Fitnesse? Если впридачу к ним будут скриншоты для визуального восприятия, то будет еще лучше.

    [Ответить]


  2. Pingback : OpenQuality.ru | Качество программного обеспечения | May 1, 2013

    […] В блоге опубликована статья FitNesse: этюд в БАГровых тонах. […]



Добавить комментарий

Пожалуйста, исправьте результат: дважды два равно



КРАТКОЕ СОДЕРЖАНИЕ

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


ПУТЕВОДИТЕЛЬ

Список всех статей с краткой аннотацией и разбивкой по рубрикам. Открыть карту.

ПОДПИСКА

Доступ к самым интересным материалам по электропочте и RSS. Подробности.

ИЩЕЙКА