Вообще, при знакомстве с UIAutomation возникает большое количество вопросов. Одним из них, и, как по мне, наиболее важным, является то, что нативно инструмент поддерживает только один тест.
Выглядит это так. Вы просто указываете файл с именем теста в окне инструмента. И выполняться будет ровно этот один тест. Если хотите другой тест, - останавливаете текущий, указываете новый и запускаете его на выполнение. И так до тех пор пока у вас не кончатся тесты...
Мое терпение лопнуло раньше. Посему, поделюсь как удалось решить эту проблему.
Для начала, расскажу о директиве #import, которую Apple добавила в свою реализацию JS для этого инструмента. Директива имеет сложное и непонятное название, а действие ее сводится к подключению других JS файлов с какими-нибудь нужными классами, фукнциями и даже данными. Если файл лежит в той же папке, что и исходный, просто укажите его имя в двойных кавычках. Если нет, - указываете относительный путь к нему. Например, так:
#import "Test.js";
или так
#import "Tests/SmokeTest.js";
Эта команда позволит нам в будущем строить нормальный фреймворк для нашего приложения.
Теперича хочется чуток подотвлечься и поговорить о том, что такое тест, но только не тест вообще, а автомейшен тест. По большому счету, это просто набор команд с входящими и исходящими данными. Входящие данные - это, обычно, какие-нибудь инициализационные значения, исходящие - результат теста в виде pass/fail (если кому нравится можно хоть в булевых значениях определять :) Еще у теста должно быть имя, дабы отличить эту штуковину от собратьев. Если кому это напомнило функцию, то это, как говорил Винни, неспроста - функция и есть, ну, или метод.
Так, ладненько, теперь так же быстро перепрыгнем на организацию тестовых наборов. Мне лично (не удивительно, почти 100% стандарт) очень по душе концепция тест-классов и тест-методов, согласно которой у вас есть несколько тест-классов, содержаших, в свою очередь, набор методов - тестов. Каждый класс относится или к какой-либо однородной группе функциональности, или объединяет тесты, схожие по реализации. Лучше, когда выбран первый вариант, который про однородность функционала, - жить потом проще :)
Ффух, наотступались. Кстати, за что люблю такие отступления, так это за то, что потом можно, например, не объяснять, что за классы и методы я сейчас буду писать :) Для облегчения работы с тестами напишем простенький класс, в котором будем хранить имя теста и результат его прогона:
/** @class Represents a test
* @author Sergey Lyopka
*/
function Test(/**String*/Name, /**bool*/Result) {
var name = Name;
var result = Result;
/** Returns the test name.
* @type String
*/
this.getName = function () { return name; }
/** Returns the test result.
* @type bool
*/
this.getResult = function () { return result; }
}
Я не буду особо расписывать, идея, думаю, понятна, а реализовать можно как угодно. Замечу только, что это не тест-метод, просто некоторая сущность, используемая для удобства.
Теперь, давайте посмотрим собственно на тест-класс и тест метод в нем:
// Test Class for FileManager tests
function FileManagerTests() {
/*Covered test cases:
* 16756
*/
this.test16756 = function (log) {
log.message("Covered Test Cases: 16756")
testResult = true;
var window = new MainWindow();
waitForVisible(window.NavBar(), 10);
if (!window.Table()) { log.debug("Table view was not found."); testResult = false; }
else {
if (1 != window.Table().cells().length) {
log.debug("Unexpected number of visible table cells was found: " +
window.Table().cells().length); testResult = false;
}
log.debug("Unexpected number of visible table cells was found: " +
window.Table().cells().length); testResult = false;
}
if (!window.LocalFolder()) {
log.debug("Local Folder was not found");
testResult = false;
}
log.debug("Local Folder was not found");
testResult = false;
}
}
return testResult;
} //test16756
}
В принципе, тоже все понятно: тест-класс FileManagerTests, сождержащий тест-метод test16756. Если код метода вызывает некоторые сомнения по поводу того, поддерживаются или нет такие функции как, скажем, waitForVisible, просто не обращайте внимания, - это реальный тест, который успользует кучу утилитных фукнций-оберток, которые были дописаны для облегчения работы с тестами. Даст Бог, и про них как-нибудь расскажу.
Осталось показать, как это все выполняется. Вспоминаем, что UIA может выполнять только один тест за раз. Вот в коде файла, который был предназначен для этого самого единственного теста, мы можем теперь вызывать наши классы и тесты, собственно вот так:
#import "Lib/TestSet.js";
#import "Lib/Logger.js";
#import "Tests/SmokeTest.js";
//Logger Init
var log = new Logger(true, false);
log.start("Smoke Test");
//Test Set filling
var testSet = new TestSet();
testSet.addTests(new SmokeTest(), log); //собственно, это и есть добавление
//Test Results
tests = testSet.getTestSet();
for(test in tests)
if (tests[test].getResult()) log.pass(tests[test].getName());
else log.fail(tests[test].getName());
if(testSet.getResult()) log.message("Test Set has completed successfully.");
else log.message("Test Set has completed unsuccessfully.");
Чтобы до конца понять, как строка testSet.addTests(new SmokeTest(), log); способствует выполнению тестов, осталось рассмотреть вспомогательный класс TestSet, который служит для формирования тестовых наборов.
#import "Test.js";
/** @class Represents a test set.
* @author Sergey Lyopka
*/
function TestSet(){
var tstSet = new Array();
var result = true;
/** Adds single test to the test set.
* @param {String} testName The name of the adding test.
* @param {bool} testResult The result the test execution.
* @type null
*/
this.addTest = function(testName, testResult){
if(!testResult) result = testResult;
tst = new Test(testName, testResult);
tstSet.push(tst);
}// addTest
/** Adds entire test class to the test set.
* @param {Test} testClass The name of the adding test class.
* @param {Logger} log The current thread logger.
* @type null
*/
this.addTests = function(testClass, log){
var testNames = getTests(testClass);
for(var i in testNames){
log.message(testNames[i] + " started.");
try{
this.addTest(testNames[i], testClass[testNames[i]](log));
}
catch(err){
log.issue(testNames[i] + " test has terminated abnormally!");
this.addTest(testNames[i], false);
}//catch
log.message(testNames[i] + " finished.");
}
}// addTests
/** Returns the result of test set execution.
* If all the tests passed, then result is true.
* If at least one test failed, then result is false.
* @type bool
*/
this.getResult = function(){return result;}
/** Returns array with all the tests.
* @type Array
*/
this.getTestSet = function(){return tstSet;}
/** Gets all the tests (methods with 'test' at the name) from the test class. Internal.
* @param {Test} testClass
* @type Array
* @ignore
*/
function getTests(testClass){
var testNames = new Array();
for (var i in testClass) {
name = i.toString();
if(name.match("test")) testNames.push(name);
} //for
return testNames;
} //getTests
}
- Возможность добавлять тесты по одному. Очень полезная возможность, скажем на этапе дебага, или выборочного формирования набора для прогона. Для этого был сделан метод this.addTest.
- this.addTests метод уже служит для добавления всех тест методов какого-либо тест-класса. Для того, чтобы получить эти методы используется функция getTests. она пробегается по всем элементам класса (спасибо создателям JS за такую возможность) и выбирает методы, в имени которых есть "test". Это ограничение было введено, чтобы иметь возможность добавлять в классы какие-либо вспомогательные функции и быть уверенным, что они не будут распознаны как тесты.
- Возможность получить результаты прогона тест-набора.
Про параметр log можно пока не здумываться, я про это расскажу отдельно, он нужен, чтобы использовать кастомизированное логирование, но опять же, на данный момент это не важно, можете его хоть убрать, если сильно глаза режет :)
Собственно, это все, что я хотел рассказать на сегодня. Я уверен, что это решение можно сильно улучшить, но мне его хватает для того, чтобы нормально структурировать свои тест наборы и относительно прозрачно ими управлять.
Если улучшите, поделитесь, - будет повод написать о рефакторинге тестов :)
Комментариев нет:
Отправить комментарий