Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

Grible – разумное хранение тестовых данных [Перевод]


(Maksym Barvinskyi) #1

Оригинал статьи на украинском языке смотрите здесь.

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

Итак, как вы догадались, я бы хотел поговорить о тестовых данных для автоматизированных тестов. Скажите, что вам хочется сделать, когда вы видите следующий код?

public void setFirstName(String name) { 
   WebElement edtFirstName = driver.findElement(By.id(“firstname”)); 
   edtFirstName.clear(); 
   edtFirstName.sendKeys(name); 
} 

public void setUserDetails(String firstName, String lastName, String email) { 
   WebElement edtFirstName = driver.findElement(By.id(“firstname”)); 
   edtFirstName.clear(); 
   edtFirstName.sendKeys(firstName); 
   WebElement edtLastName = driver.findElement(By.id(“lasttname”)); 
   edtLastName.clear(); 
   edtLastName.sendKeys(lastName); 
   WebElement edtEmail = driver.findElement(By.id(“email”)); 
   edtEmail.clear(); 
   edtEmail.sendKeys(email); 
} 

Кто-то, конечно, ответит вопросом на вопрос: а что это за язык вообще? Отношусь с уважением к людям, которым важны детали. Это Java. Не знаю, как вам, а мне хочется, во-первых, заменить первые 3 строки метода setUserDetails вызовом метода setFirstName, а во-вторых, вынести последовательность действий clear() и sendKeys() в отдельный метод. Тем, кто этого не захотел сделать, предлагаю почитать бестселлер умного дяди Мартина Фаулера “Рефакторинг”. Это делается с той целью, чтобы при изменении реализации метода setFirstName мы автоматически внесли изменения и в метод setUserDetails, а если обобщить, то чтобы делать любое изменение только в одном месте.

Теперь внимание вопрос! Почему бы нам не сделать то же самое и с тестовыми данными?

Вопрос риторический, не переживайте. Давайте подумаем, как это можно реализовать. Концепция должна быть такая же, как и для кода, а именно: выносить общие части в место общего доступа для тех, кому это нужно.

Я уверен, что привел вас к определенным размышлениям, которыми вы уже можете воспользоваться для вашего текущего проекта, но если вы используете подход Data-Driven в написании тестов, то для вас есть еще и готовый инструмент для реализации подобной оптимизации. Имя ему Grible (http://www.grible.org).

Часто люди (а особенно те​​, кто работал с QTP (или как его теперь называют UFT)), привыкли хранить данные в Excel файлах, также возможен вариант XML, JSON, CSV, или тупо текстовые файлы. Ну что же, давайте рассмотрим пример с тестом на логин форму. Функционал логин формы такой: если введены правильные логин и пароль, то кнопка “Log in” становится активной. Поэтому чтобы проверить эту функцию с помощью подхода Data-Driven, создадим, например, такой Excel файл:

http://www.mbarvinskyi.com/oldsite/img/my/pine/excel.png

и напишем такой тест:

public void loginValidation() { 
   loginPage = NavigationUtils.startWebApplication(); 
   loginPage.enterCredentials(data.get("Login"), data.get("Password")); 
   boolean expectedState = Boolean.parseBoolean(data.get("LoginButtonEnabled")); 
   loginPage.verifyLoginButtonEnabled(expectedState); 
} 

где data – это HashMap<String, String>, которая держит в себе колекцию пар ключ-значение текущей итерации теста. Количество итераций традиционно автоматически определяется количеством строк значений в дата-файле.

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

И здесь вам на помощь приходит Grible! Главная фишка его в том, что он реализует концепцию, которую я называю Reusable Data Storage (хранилище данных многократного использования). Выглядит оно так:

То есть в grible мы создаем хранилище (data storage) Users:

http://www.mbarvinskyi.com/oldsite/img/my/pine/storage1.png

И привязываем его три строки в файл данных нашего теста (наводя курсор на ячейку с номером строки из хранилища, нашему вниманию предстает вид этой строки в хранилище):

Теперь генерируем класс UserInfo средствами grible (More → Generate class):

И создав его в коде, делаем соответственные изменения в тесте:

public void loginValidation() { 
   loginPage = NavigationUtils.startWebApplication(); 
   UserInfo user = DataStorage.getDescriptor(UserInfo.class, data.get("User")); 
   loginPage.enterCredentials(user); 
   boolean expectedState = Boolean.parseBoolean(data.get("LoginButtonEnabled")); 
   loginPage.verifyLoginButtonEnabled(expectedState); 
} 

Page (screen) object теперь принимает не n параметров типа String, а один параметр типа UserInfo. Часто бывает так, что в такие методы нужно передать 10, а то и 20 параметров. Создание так называемого дескриптора (в нашем случае UserInfo) решает проблему гибкости и читабельности вызова таких методов.

Итак, теперь если нам нужно добавить поле Company на страницу логин формы, мы просто добавляем столбец Company к нашему хранилищу Users:

http://www.mbarvinskyi.com/oldsite/img/my/pine/storage3.png

Снова генерируем класс дескриптора (More → Generate class) и добавляем обработку этого параметра в метод enterCredentials():

public void enterCredentials(UserInfo user) { 
   edtLogin = browser.findElement(By.id("login")); 
   edtLogin.sendKeys(user.getLogin()); 

   edtCompany = browser.findElement(By.id("company")); 
   edtCompany.sendKeys(user.getCompany()); 

   edtPassword = browser.findElement(By.id("pswd")); 
   edtPassword.sendKeys(user.getPassword()); 
} 

Вот и все! Заметьте, что ни сам тест, ни файл данных я не трогал. Все гибко и все работает!

Прошу задавать вопросы и высказывать пожелания в комментариях или в личных сообщениях. Попробую на все ответить.

Всем хорошего тестирования!


Автоматизаторы, которые… что-то пописывают
(Sergey Korol) #2

А разве после добавления нового столбца, нам не нужно перегенерировать класс UserInfo, либо вручную добавить соответствующие поля / геттеры? В enterCredentials видно новый геттер getCompany, но кому-то может быть не понятно, как он там появился.


(Maksym Barvinskyi) #3

Да, конечно, класс UserInfo нужно перегенерировать. Спасибо, не заметил. Наглядно данный пример можно посмотреть на видео: http://www.grible.org/docs.php#video (там я не забыл перегенерировать класс :smile:)


(Dmytro Makhno) #4

Огромный респект человеку создавшему тул, но проблематика - не ясна.

Мне кажется, что эта штука охренна, когда есть команда “начинающих” автоматизаторов, которым реально нужно “видеть” модельку. Т.к. код они пишут гораздо медленнее чем натаскивают визарды.

Я сам не сторонник кивордов, но видел как магически они действуют на бизнесс аналитиков и “некодирующих” автоматизаторов.
Не хочется видеть замену “FitNess”, в том или ином виде.

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

** Далее, критик mode - on **

Навскидку MVC - pattern.
Model - User
View - LoginPage
Controller - WebDriver

Холиварная тема когда нужно и не нужно хранить тестовые данные отдельно от тестов. Ярый противник данного (тесты отдельно, данные отдлельно) подхода.

“FastLogin”?
Есть - тогда у вас используются “это” только в логин тестах.

Это все можно сделать и без Griddle, в чем преимущество.
Есть готовые тулы, которые по “классам”, “бд” или еще с чего могут генерить wrapperClass. MS “Entity Framework” далеко очень тут ушел. Есть три сущности БД, Модель, Классы - и все из чего-то одного можно генерить, сказав “это” первично.

Версионированность

Посмотрел презентации (честно по диагонали) и не понятно как решается “версионированность”, когда данные живут отдельно от тестов.
Правильно я понимаю, что

на вот этой картике, данный из postgre сериализуются в json, и попадают под vcs?
А что делает Grible в этом сценарии?

Как в общем случае, если я берут из vcs тесты версии n-1, получать данные из grible версии n-1?

Эта концепция называется DB. Как по мне.
Интересно можно ли хранить динамические структуры? И будет ли от этого бенефит? Мой скромный опыт NoSql, говорит, что если есть json, его можно практически сразу фигачить в бд.

public UserInfo(HashMap<String, String> data) { 
      super(data); 
      this.email = getString("Email"); 
      this.firstName = getString("FirstName"); 
      this.lastName = getString("LastName"); 
   }
public UserInfo(Dictionary<string, string> data) 
         : base(data) 
      { 
         Email = GetString("Email"); 
         FirstName = GetString("FirstName"); 
         LastName = GetString("LastName"); 
      }

красивше, эти строки занести в анотации. :slight_smile:


(Maksym Barvinskyi) #5

Спасибо за отзыв :smiley:

Попробую объяснить проблематику.
Речь здесь идет не о кейвордах, это входные данные, то есть data-driven подход. Здесь вот как раз представлена та сторона холивара, которая хранит входные данные отдельно от тестов. Если вы храните данные в коде, то конечно этот инструмент Вам будет совсем неинтересен. Я использовал оба подхода на разных продуктах, у обоих есть свои преимущества и недостатки, так вот одно из преимуществ хранения данных отдельно в том, что их может редактировать мануальный тестировщик. Я мог написать код теста с одной итерацией, а мануальщик потом мог дописать еще 2-3, что бы покрывались какие-то там особые кейсы.

Да весь код в блокноте можно написать :smiley:, но люди используют IDE. Почему? Это удобнее, то есть экономит время. Так вот grible экономит время по сравнению с использованием Excel-я для хранения входных данных, вот, собственно говоря, и всё.

Нет. У грайбла есть 2 варианта хранения данных: в базе или в json файлах, этот вариант выбирается при установке грайбла и не меняется, то есть это 2 в 1. В случае с базой всё ясно - обычное веб-приложение, а вот json-версия - это фактически десктоп приложение, но веб, то есть каждый, кто хочет отредактировать данные, должен его себе установить. Там указывается локальная папка, в которой есть json-файлы определённого формата. Эта папка помещается в папку с фреймворком и так достигается версионированость.

Я в мае планирую рассказывать и показывать грайбл, обязательну кину сюда ссылку на ивент и потом на видео с него, а то так долго это всё описывать :smile: