Есть три вещи, которые можно делать бесконечно:
1. Наблюдать, как горит огонь
2. Наблюдать, как бежит вода
3. И наблюдать, как кто-то работает
В нашем случае, наблюдать за тем, как крутятся наши over 9000 тестов. Особенно красиво смотрятся Selenium тесты. Выглядит, как будто бешеный тушканчик с вечным двигателем внутри сел тестировать систему.
Не знаю как вас, но меня это затягивает:
Остаток статьи я расскажу маленькую success-story о том, как мы организовали наше тестирование на Selenium
Это был наш второй подход к снаряду. В первый раз, еще в 2009 году, сыпалось все:
• Web драйвер работал только с IE.
• Иногда подвисал при открытии браузера
• Иногда не мог закрыть браузер
• И самое главное: встроенная запись тестов через расширение браузера (т.н. «накликивание») делала плохие тесты для Ext.Js оболочки.
Наш проект написан с использованием Ext.Net – оболочки Ext.Js для .NET. А Ext.Js был явно слишком сложен для Selenium. Он генерировал для клиента случайные идентификаторы элементов, и дополнительно случайные идентификаторы объектов так-же генерировали мы сами (что необходимо для параллелизма и высоких нагрузок нашей платформы).
Вроде бы тест можно легко накликать мышкой, но время жизни этого теста составляло – считанные дни. Хуже того, когда падал тест, то понять причину падения часто было сложней, чем просто перенакликать его заново (что и делали пару раз).
А еще тесты были медленные! Представьте сценарий:
1. Мы вбиваем строку поиска в таблицу с данными
2. Сервер возвращает ответ – пустой грид (что иногда может быть правильным)
3. И вдогонку шлет сообщение об ошибке, если она произошла.
Ext.Js с его асинхронными запросами на любой клик устроен так, что для многих вызовов нам нужно ждать сообщение об ошибке секунд 10 и если мы его не получали, то считали, что поиск отработал нормально.
10 секунд здесь, 10 там и в итоге сам тест на 90% времени состоит из ожидания.
И это еще не все. А вы пробовали разобраться в причинах падения накликанного теста из Selenium лога? Вот так, чтобы повторно не запускать тест, а окинуть взглядом лог и понять суть проблемы. У меня – не получалось.
Через пару месяцев стало понятно, что такое тестирования отнимает у нас больше времени чем приносит пользы и Selenium тесты мы убили.
Проект тем временем все рос, количество функций измерялось тысячами и вот, год назад героический selmaril вернулся к этой проблеме с обновленным Selenium и обновленным пониманием, как тестировать Ext.Js
Его решение было: писать API.
Мы решили отказаться от записи тестов мышкой, но прийти к возможности записывать NUnit(!) тесты с короткой нотацией в виде:
1. Отфильтровать пользователей по дате создания = сегодня.
2. Отсортировать по имени
3. Открыть первую запись
4. Изменить пароль.
5. Сохранить.
6. Нажать ОК, если выскочит окно «Вы уверены».
Для этого было нужно API над Selenium и NUnit, которое само состояло бы из очень простых элементов, но позволяющих оперировать не мышкой и DOM моделью, а с объектами интерфейса Ext.Js.
Месяц напряженной работы, и первая версия API появилась.
/// <summary>
/// DocumentOperationGridOperationTest
/// </summary>
[Test]
public void DocumentOperationGridOperationTest()
{
var baseDocName = typeof(BaseInDocument).ModelName();
var implDocName = typeof(BaseInDocumentImpl).ModelName();
using (var grid = Env.Navigation.OpenList(implDocName))
{
var operationCaption = "Документик_Создайся";
grid.Toolbar.Click(operationCaption);
var docForm = Env.TabPanel.GetForm(implDocName);
docForm.Toolbar.Click("Создание", "Завершить операцию {0}".FormatWith(operationCaption));
docForm.Close();
}
using (var grid = Env.Navigation.OpenList(baseDocName))
{
var operationCaption = "Редактируем_Наследник";
grid.Data.First().Select();
grid.Toolbar.Click("Операции", operationCaption);
grid.Toolbar.Click("Открыть");
var docForm = Env.TabPanel.GetForm(implDocName);
var fieldValue = docForm.GetField<TextField>("SomeNewField");
Assert.False(string.IsNullOrEmpty(fieldValue.GetValue()), "Не заполнилось поле которое должно быть заполнено в классе операции, то есть не вызвался класс операций для документа");
grid.DeleteFirstRow();
docForm.Close();
}
}
/// <summary>
/// Получить элемент из контекстного меню
/// </summary>
/// <param name="fieldName">Системное имя поля сущности, в ячейке которой у данной записи надо вызвать контекстное меню</param>
/// <param name="buttonCaption">Путь к элементу контекстного меню, по которому надо получить элемент (кнопку действия например)</param>
/// <returns>Найденный элемент в контекстном меню</returns>
public IToolbarElement GetContextMenuItem(string fieldName, string[] buttonCaption)
{
var buttonsFullPath = Oreodor.Utils.EnumerableExtensions.ToString(buttonCaption, "->");
Env.AddHistory("Получить элемент в контекстном меню ячейки для записи с Id - SysName = {0} - {1}, для колонки {2}, по пути '{3}'".FormatWith(
Id,
this.Data.ContainsKey("SysName") ? this.Data["SysName"] : string.Empty,
fieldName,
buttonsFullPath));
IToolbarElement menuItem = null;
if (buttonCaption.Length > 0)
{
var menuItems = new List<IToolbarElement>();
if (IsContextMenuVisible(fieldName))
{
menuItems.AddRange(GetCurrentContextMenu());
}
else
{
menuItems.AddRange(ShowContextMenu(fieldName));
}
menuItem = Toolbar.CheckItem(menuItems, buttonCaption[0]);
if (buttonCaption.Length > 1)
{
for (var i = 1; i < buttonCaption.Length; i++)
{
var menuCaption = buttonCaption[i - 1];
var itemCaption = buttonCaption[i];
var menu = menuItem.Menu.ToList();
Assert.That(menu.Count() != 0, "Меню '{0}' не должно быть пустым, т.к. в нем ещё надо найти '{1}'.".FormatWith(menuCaption, itemCaption));
var candidateToolbarItem = Toolbar.CheckItem(menu, itemCaption);
Assert.That(candidateToolbarItem != null, "Проверка наличия элемента '{0}' в меню '{1}'. Найдены следующие элементы: {2}".FormatWith(itemCaption, menuCaption, menu.Aggregate(", ", (aggregated, item) => aggregated + "'" + item.Text + "'")));
menuItem = candidateToolbarItem;
}
}
}
return menuItem;
}
Также была решена важная проблема – быстрое понимание ошибки из лога TeamCity. Api генерирует вот такой отчет по каждому тесту.
Итог: сейчас мы покрыли около 80% поведения интерфейса системы. Делюсь выжимкой нашего опыта, для тестирования Ext.Js на Selenium Вам нужно писать свою обертку для тестирования для решения следующих проблем:
1. Лаконичность и понятность теста
2. Устойчивость теста к изменением системы
3. Скорость работы тестов
4. Легко разобраться в причинах поломки теста
Тесты, созданные накликиванием из интерфейса браузера скорее всего Вам не подойдут.
P.S.
Если у вас Ext.Js проект и вы решите покрыть его Selenium тестами — обращайтесь за советами, постараемся помочь.
Автор: Joshua