TestComplete: какой код выполняется первым или проблема JScript Global Code

Какой код выполняется первым, когда мы запускаем тест в TestComplete? Новичок скажет, что первой выполнится первая строка теста, более опытный пользователь TestComplete вспомнит про обработчик события OnStartTest, однако в реальности всё может оказаться сложнее. Именно такую сложную ситуацию мы разберём в этой статье.

Для чистоты эксперимента создадим новый Project Suite с новым пустым проектом на JScript (для этого при создании проекта отключите опцию Generate default content). Добавим в проект новый модуль Tests с одной простой функцией Test1 и создадим обработчик события OnStartTest (его можно поместить куда угодно, например в отдельный модуль Events):

function Test1(){
  Log.Message('This is a message from test');
}
function GeneralEvents_OnStartTest(Sender){
  Log.Message('This is a message from OnStartTest event')
}

Теперь запустим наш Test1 (либо просто как функцию, либо создав для него Test Item) и посмотрим на результат:

Пока что всё правильно: сначала вызывается код из OnStartTest, затем код из теста. Теперь создадим новый модуль (это важно: именно новый!) с именем Other и поместим в него следующий код:

function globalCodeFunction(){
  Log.Message('This function is called from JScript Global Code');
  generateError();
 }

function generateError(){
  Log.Error('An error from nested global function');
  generateWarning();
 }

function generateWarning(){
  Log.Warning('An error from nested global function');
 }

var CONSTANT1 = 12;
globalCodeFunction();
var CONSTANT2 = 'string constant';

Здесь у нас есть функция globalCodeFunction, которая вызывается в предпоследней строке модуля и сама при этом вызывает 2 функции, одна из которых генерирует ошибку, другая предупреждение, причем функция generateWarning вызывается из функции generateError. Теперь запустим снова наш Test1 из модуля Tests и посмотрим на результат:

Как видим, глобальный код запустился раньше всех, даже до функции OnStartTest. Причём обратите внимание, что мы не включали новый модуль Other в модуль Tests с помощью //USEUNIT, т.е. теоретически этот код вообще не должен был запускаться!

Кстати, я намеренно поместил вызов функции globalCodeFunction между объявлениями констант чтобы продемонстрировать, что подобный вызов глобальной функции может оказаться где угодно. Например, если у вас есть модуль Constants, в котором вы просто объявляете константы, однажды вам может понадобиться задавать значение константы в зависимости от внешних условий. Допустим, вы захотите иметь константу SERVER_NAME, в которой будет храниться имя текущего тестового сервера, а имя сервера будет возвращаться функцией getCurrentServerName, которая, в свою очередь, может выполнять кучу других вызовов.

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

Теперь откройте сгенерированный лог, выделите по очереди ошибку, а затем предупреждение, и сравните для них содержимое вкладки Call Stack:

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

Конечно, в нашем простом примере мы можем отследить порядок вызовов по сообщениям в логе, однако эти сообщения мы добавили сами. Представьте, что у вас есть вызов 5 функций друг из друга, при этом пятая функция генерирует предупреждение, а предыдущие 4 не кидают в лог никаких сообщений. В этом случае вам придётся исследовать код, чтобы отследить все вызовы, причём это может оказаться весьма сложной задачей!

Теперь предположим, что вы хотите в отладочном режиме пройти проблемный код и найти причину ошибки. Поставьте брейкпоинт на строке с вызовом Log.Error в функции generateError и запустите тест снова. Вы обнаружите, что скрипт отработает без остановки, таким образом не давая вам возможности войти в режим отладки.

Проблема №3: debug-режим не работает в глобальном коде.

Чтобы не сталкиваться с описанными проблемами достаточно не использовать в глобальном коде вызовы функций. Вместо этого поместите подобные вызовы в обработчик события OnStartTest. Если же вам не повезло столкнуться с подобным кодом в существующем проекте, причём код этот запутанный и избавиться от глобальных вызовов очень сложно, можно частично решить проблему, включив Call Stack для предупреждений и обычных сообщений. Для этого можно написать функцию:

function enableCallStackSettings(){
  Log.CallStackSettings.EnableStackOnWarning = true;
  Log.CallStackSettings.EnableStackOnEvent = true;
  Log.CallStackSettings.EnableStackOnFile = true;
  Log.CallStackSettings.EnableStackOnImage = true;
  Log.CallStackSettings.EnableStackOnLink = true;
  Log.CallStackSettings.EnableStackOnMessage = true;
}

Причём вызывать эту функцию необходимо в начале каждой глобальной функции.