Silktest: Вывод результатов в собственном формате

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

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

  1. неудобство чтения стандартного вывода
  2. необходимость использования каких-то других внешних средств для обработки результатов (например результаты некоторых тестов должны быть в дальнейшем заимпортированы в какую-то базу или в какую-то систему)
  3. нужно более наглядное представление результатов.

Этот список можно продолжать до бесконечности. 

Рассмотрим подход по организации своего вывода результатов. Рассмотрим вывод в некоторый текстовый формат, так как это наиболее частый случай использования своей системы вывода результатов. Более конкретизируем задачу: нам надо делать вывод результатов в HTML-формате. То есть, при старте скрипта у нас создается некоторый HTML-файл и по ходу выполнения кода этот файл наполняется информацией.

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

  • Print - выводит обычный текст
  • ListPrint - выводит список в виде обычного текста, где каждый элемент списка представляет собой отдельную строку
  • LogWarning - выводит текст предупреждения. Это еще не ошибка, но уже текст, который своей подсветкой обращает на себя внимание. Внутренний счетчик увеличивает количество вызванных предупреждений.
  • LogError - выводит сообщение об ошибке. При этом счетчик ошибок увеличивает свое значение на 1.
  • ExceptPrint - выводит информацию о сгенерированном исключении

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

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

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }[ ] private STRING sFileName [ ] window MyLoggerClass Log [ ] [+] winclass MyLoggerClass [ ]{/syntaxhighlighter}

В результате выполнения данного метода, все аргументы, которые переданы в метод, будут склеены в строку. Эта строка будет помещена внутрь конструкции <font face="Tahoma" color="Black">...</font> и в последствии записана в файл, причем данная строка дополняет уже существующий файл или создает новый файл, если файла с именем sFileName еще нет.

Идем дальше. Аналог функции ListPrint реализуется, используя метод Message. Итак, реализация данного метода имеет вид:

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }[+] winclass MyLoggerClass [ ] ............................................................... [+] VOID ListPrint( LIST OF ANYTYPE laValue ) [ ] ANYTYPE aValue [ ] [+] for each aValue in laValue [ ] this.Message(aValue){/syntaxhighlighter}

Теперь, реализуем метод-аналог функции LogWarning. Он будет выдавать в файл сообщение, подсвеченное цветом с кодом FF00FF. Это цвет по умолчанию для предупреждений в СилкТесте. Код имеет вид:

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }[+] winclass MyLoggerClass [ ] ............................................................... [+] VOID Warning( STRING sWarning ) [ ] LogWarning( sWarning ) [ ] this._writeFile( "<br><font face=""Tahoma"" color=#FF00FF>{sWarning}</font>" ){/syntaxhighlighter}

Теперь, по аналогии сделаем обертку для LogError с одной небольшой поправкой: ошибки могут быть критичными (нужно немедленно выйти из тесткейса) и некритичными (можно продолжать выполнение тесткейса). Это можно учесть дополнительным параметром, отвечающим за критичность. Код имеет вид:

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }[+] winclass MyLoggerClass [ ] ............................................................... [+] VOID Error( STRING sError , BOOLEAN bCritical optional ) [ ] LogError( sError ) [ ] [ ] this._writeFile( "<br><font face=""Tahoma"" color=#FF0000>{sError}</font>" ) [ ] [+] if( !IsNull( bCritical ) && bCritical ) [ ] raise -1 , sError{/syntaxhighlighter}

По умолчанию вызов данного метода исключения не вызывает, но в любом случае пишет в файл строку ошибки, выделенную красным цветом. И последний метод, это метод, который выдает информацию о сгенерированном исключении. Метод выглядит так:

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }[+] winclass MyLoggerClass [ ] ............................................................... [+] VOID ExceptWrite() [ ] CALL_LIST lpCalls [ ] CALL pCall [ ] [ ] ExceptPrint() [ ] [ ] lpCalls = ExceptCalls() [ ] pCall = lpCalls[1] [ ] this.Message( "{ExceptData()}" ) [ ] this.Message("Occured in {pCall.sFunction} {pCall.sModule} ({pCall.iLine})") [ ] ListDelete(lpCalls,1) [ ] [+] for each pCall in lpCalls [ ] this.Message("Called from {pCall.sFunction} {pCall.sModule} ({pCall.iLine})"){/syntaxhighlighter}

Теперь немного косметики. Нам надо как-то отметить, что тесткейс начал выполнение и завершил его. Для этого допишем 2 метода (не будем заморачиваться пока в деталях, главное - это принцип):

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }[+] winclass MyLoggerClass [ ] ............................................................... [+] VOID StartTC() [ ] this._writeFile("<br><h1>Test case: {GetTestCaseName()}</h1>") [ ] [+] VOID StopTC( BOOLEAN bException ) [+] if( bException ) [ ] this.Error("Exception: {ExceptData()}") [ ] this.ExceptWrite() [ ] ExceptClear() [ ] this.Message("<br>Test case <b>{GetTestCaseName()}</b> is completed"){/syntaxhighlighter}

Пока не будем вдаваться в подробности, почему эти методы имеют такой вид. Будем считать, что так надо.

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

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }[+] winclass MyLoggerClass [ ] ............................................................... [+] VOID Init() [ ] sFileName = "{GetProgramDir()}"\Report_{FormatDateTime(GetDateTime(),"yyyymmddhhnnss")}.htm"{/syntaxhighlighter}

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

Всё, класс готов. Теперь начнем приспосабливать его к системе. Объект, через который мы будем взаимодействовать с системой через объект Log - экземпляр класса MyLoggerClass. Этот экземпляр мы создали в самом начале.

Перейдем к следующему этапу. Нам нужно, чтобы при каждом запуске скрипта (независимо от того, были ли там тесткейсы или нет) наш файл результатов создавался (нужно вызвать метод Init). При начале выполнения тесткейса нам нужно вызвать метод StartTC, а по завершении - StopTC, чтобы нужные реквизиты тесткейса были внесены в файл результатов.

Для этого нам нужно привлечь recovery-систему. Что это такое и с чем его едят, можно подробно прочитать в книге Руководство по Borland SilkTest в 3-й главе. В данном случае нам нужны функции:

  • ScriptEnter - запускается непосредственно перед первым тесткейсом текущего файла скрипта
  • ScriptExit - запускается сразу после последнего тесткейса текущего файла скрипта
  • TestCaseEnter - запускается непосредственно перед каждым тесткейсом
  • TestCaseExit - запускается сразу после каждого тесткейса

В принципе, эти функции мы можем описать прямо в текущем файле, в котором и описали класс. Но подобные функции скорее специфичны для проекта, а не для класса. А класс-то как раз более-менее общий. Соответственно, рядом с файлом MyLogger.inc создадим файл Recovery.inc со следующим содержимым:

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }[ ] use "MyLogger.inc" [ ] [+] ScriptEnter() [ ] Log.Init() [ ] DefaultScriptEnter() [ ] [+] ScriptExit( BOOLEAN bException ) [+] if( bException ) [ ] Log.ExceptWrite() [ ] DefaultScriptExit( bException ) [ ] [+] TestCaseEnter() [ ] Log.StartTC() [ ] DefaultTestCaseEnter() [ ] [+] TestCaseExit( BOOLEAN bException ) [ ] Log.StopTC( bException ) [ ] DefaultTestCaseExit( bException ){/syntaxhighlighter}

Вот и все. Теперь осталось в своих скриптах подключить именно этот файл Recovery.inc (если вы выполняете еще какие-то действия в функциях recovery-системы, то добавляйте их в этот файл, куда нужно) и вместо привычных Print, ListPrint, LogWarning, LogError, ExceptPrint использовать соответственно Log.Message, Log.ListPrint, Log.Warning, Log.Error.

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