Статьи

Написание приемочных тестов для приложений Openshift и MongoDb

Приемочное тестирование используется для определения соответствия требованиям спецификации. Он должен работать в среде, максимально похожей на рабочую. Поэтому, если ваше приложение развернуто в Openshift, вам потребуется параллельная учетная запись с той, которая используется в рабочей среде для запуска тестов. В этом посте мы собираемся написать приемочный тест для приложения, развернутого в  Openshift, которое использует MongoDb в качестве базы данных.


Развернутое приложение представляет собой простую библиотеку, которая возвращает все книги, доступные для кредитования.
Это приложение использует
MongoDb  для хранения всей информации, связанной с книгами.

Итак, давайте начнем с описания цели, функции, пользовательской истории и критериев приемлемости для предыдущих приложений.
Цель : расширение лекции для большинства людей.
Особенность : Показать доступные книги.
История пользователя : Просмотр каталога -> Чтобы найти книги, которые я хотел бы позаимствовать, как пользователь, я хочу иметь возможность просматривать все книги.
Критерии приемки : должны видеть все доступные книги.
Сценарий:

Учитывая, что я хочу взять книгу

Когда я нахожусь на странице каталога

Тогда я должен увидеть информацию о доступных книгах: Властелин банок — 1299 — LOTRCoverUrl, Хоббит — 293 — HobbitCoverUrl

Обратите внимание, что это очень простое приложение, поэтому критерии принятия тоже просты.

Для этого примера нам понадобятся две тестовые среды: первая для написания и запуска приемочных тестов, а другая для управления 
бэкэндом
NoSQL . В этом посте мы собираемся использовать
Thucydides для
ATDD и
NoSQLUnit для работы с
MongoDb .

Приложение уже развернуто в
Openshift , и вы можете взглянуть на 
https://books-lordofthejars.rhcloud.com/GetAllBooks

Thucydides
— это инструмент, разработанный для облегчения написания автоматизированных приемочных и регрессионных тестов. 
Thucydides использует
API WebDriver для доступа к
элементам
HTML- страницы. Но это также помогает вам организовать ваши тесты и пользовательские истории с помощью конкретной модели программирования, создавать отчеты о выполненных тестах и, наконец, также измерять функциональное покрытие. 

Чтобы написать приемочные тесты с
Thucydides следующие шаги должны быть выполнены. 
  • Прежде всего, выберите пользовательскую историю одной из ваших функций. 
  • Затем реализуйте класс PageObject . PageObject — это шаблон, который моделирует элементы пользовательского интерфейса веб-приложения как объекты, поэтому тесты могут взаимодействовать с ними программно. Обратите внимание, что в этом случае мы кодируем «как» мы получаем доступ к HTML- странице.
  • Следующим шагом является реализация библиотеки шагов. Этот класс будет содержать все шаги, необходимые для выполнения действия. Например, для создания новой книги необходимо открыть страницу addnewbook , вставить новые данные и нажать кнопку «Отправить». В этом случае мы кодируем «что» нам нужно для реализации критериев приемлемости.
  • И, наконец, кодирование выбранной пользовательской истории в соответствии с определенными критериями принятия и использованием предыдущих классов шагов.
NoSQLUnit — это
расширение
JUnit, которое нацелено на управление жизненным циклом необходимого
движка
NoSQL , помогает нам поддерживать базу данных в известном состоянии и стандартизировать способ написания тестов для
приложений
NoSQL .
NoSQLUnit состоит из двух групп
правил
JUnit и двух аннотаций. В текущем случае нам не нужно управлять жизненным циклом
движка
NoSQL , потому что он управляется внешним объектом (
Openshift ).


Итак, давайте приступим к работе:

первое, что мы собираемся сделать, это создать класс объектов, который не содержит тестовый код; он используется как способ представления структуры требований.

public class Application {

    @Feature
    public class Books {
        public class ListAllBooks {}
    }

}

Обратите внимание, что каждая реализованная функция должна содержаться в классе с
аннотацией
@Feature . Каждый метод избранного класса представляет пользовательскую историю.


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

<table id="listBooks" cellspacing="0" cellpadding="5">
<caption>List of Available Books</caption>
<tr>
<th>Title</th>
<th>Number Of Pages</th>
<th>Cover</th>
</tr>
                .....
</table>

Самое главное здесь в том , что
таблица  теги имеют
идентификаторы имени 
listBooks , которые будут использоваться в классе PageObject , чтобы получить ссылку на его параметры и данные. Давайте напишем объект страницы:

@DefaultUrl("http://books-lordofthejars.rhcloud.com/GetAllBooks")
public class FindAllBooksPage extends PageObject {

@FindBy(id = "listBooks")
private WebElement tableBooks;

public FindAllBooksPage(WebDriver driver) {
super(driver);
}

public TableWebElement getBooksTable() {

Map<String, List<String>> tableValues = new HashMap<String, List<String>>();

tableValues.put("titles", titles());
tableValues.put("numberOfPages", numberOfPages());
tableValues.put("covers", coversUrl());

return new TableWebElement(tableValues);
}

private List<String> titles() {
List<WebElement> namesWebElement = tableBooks.findElements(By.className("title"));
return with(namesWebElement).convert(toStringValue());

}

private List<String> numberOfPages() {
List<WebElement> numberOfPagesWebElement = tableBooks.findElements(By.className("numberOfPages"));
return with(numberOfPagesWebElement).convert(toStringValue());
}

private List<String> coversUrl() {
List<WebElement> coverUrlWebElement = tableBooks.findElements(By.className("cover"));
return with(coverUrlWebElement).convert(toImageUrl());
}

private Converter<WebElement, String> toImageUrl() {
return new Converter<WebElement, String>() {

@Override
public String convert(WebElement from) {
WebElement imgTag = from.findElement(By.tagName("img"));
return imgTag.getAttribute("src");
}
};
}

private Converter<WebElement, String> toStringValue() {
return new Converter<WebElement, String>() {

@Override
public String convert(WebElement from) {
return from.getText();
}
};
}

}

Используя
@DefaultUrl, мы устанавливаем, какой URL отображается, с помощью
@FindBy мы отображаем веб-элемент с помощью id
listBooks и, наконец,
метод getBooksTable (), который возвращает содержимое сгенерированной
HTML- таблицы.

Следующее, что нужно сделать, это реализовать класс steps; в этом простом случае нам нужно всего два шага: первый, который открывает
страницу
GetAllBooks , и другой, который утверждает, что таблица содержит ожидаемые элементы.

public class EndUserSteps extends ScenarioSteps {

public EndUserSteps(Pages pages) {
super(pages);
}

private static final long serialVersionUID = 1L;

@Step
public void should_obtain_all_inserted_books() {
TableWebElement booksTable = onFindAllBooksPage().getBooksTable();

List<String> titles = booksTable.getColumn("titles");
assertThat(titles, hasItems("The Lord Of The Rings", "The Hobbit"));

List<String> numberOfPages = booksTable.getColumn("numberOfPages");
assertThat(numberOfPages, hasItems("1299", "293"));

List<String> covers = booksTable.getColumn("covers");
assertThat(covers, hasItems("http://upload.wikimedia.org/wikipedia/en/6/62/Jrrt_lotr_cover_design.jpg", "http://upload.wikimedia.org/wikipedia/en/4/4a/TheHobbit_FirstEdition.jpg"));
}

@Step
public void open_find_all_page() {
onFindAllBooksPage().open();
}

private FindAllBooksPage onFindAllBooksPage() {
return getPages().currentPageAt(FindAllBooksPage.class);
}

}

И, наконец, класс для проверки критериев приемлемости:

@Story(Application.Books.ListAllBooks.class)
@RunWith(ThucydidesRunner.class)
public class FindBooksStory {

private final MongoDbConfiguration mongoDbConfiguration = mongoDb()
.host("127.0.0.1").databaseName("books")
.username(MongoDbConstants.USERNAME)
.password(MongoDbConstants.PASSWORD).build();

@Rule
public final MongoDbRule mongoDbRule = newMongoDbRule().configure(
mongoDbConfiguration).build();

@Managed(uniqueSession = true)
public WebDriver webdriver;

@ManagedPages(defaultUrl = "http://books-lordofthejars.rhcloud.com")
public Pages pages;

@Steps
public EndUserSteps endUserSteps;

@Test
@UsingDataSet(locations = "books.json", loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
public void finding_all_books_should_return_all_available_books() {
endUserSteps.open_find_all_page();
endUserSteps.should_obtain_all_inserted_books();
}

}

Есть несколько вещей, которые следует рассмотреть в предыдущем классе:

  • @Story должен получить класс, определенный с помощью аннотации @Feature , чтобы Thucydides мог правильно создать отчет.
  • Мы используем MongoDbRule для установления соединения с удаленным экземпляром MongoDb . Обратите внимание, что мы можем использовать адрес localhost из-за возможности переадресации портов Openshift , поэтому, хотя localhost используется, мы действительно управляем удаленным экземпляром MongoDb .
  • Использование @Steps Thucydides создаст экземпляр библиотеки предыдущего шага.
  • И, наконец, аннотация @UsingDataSet для заполнения данных в базе данных MongoDb перед запуском теста.
{
"book":[
{
    "title": "The Lord Of The Rings",
    "numberOfPages": "1299",
    "cover": "http:\/\/upload.wikimedia.org\/wikipedia\/en\/6\/62\/Jrrt_lotr_cover_design.jpg"
},
{
"title": "The Hobbit",
"numberOfPages": "293",
"cover": "http:\/\/upload.wikimedia.org\/wikipedia\/en\/4\/4a\/TheHobbit_FirstEdition.jpg"
}
]
}


Обратите внимание, что
NoSQLUnit поддерживает базу данных в известном состоянии, очищая базу данных перед каждым выполнением теста и заполняя ее известными данными, определенными в
файле
json .

Также имейте в виду, что этот пример очень прост, поэтому
было показано только небольшое подмножество возможностей
Thucydides и
NoSQLUnit . Следите за обоими сайтами: 
http://thucydides.info и 
https://github.com/lordofthejars/nosql-unit.

Мы продолжаем учиться,

Алекс.


Любовь — это горящая вещь, и она создает огненное кольцо, связанное диким желанием, я попадаю в кольцо огня (кольцо огня — Джонни Кэш)