Selenium * TestNG

advertisement
Бодрящий микс из Selenium и TestNG
Регрессионное тестирование руками
разработчиков
Ребров Андрей
Luxoft
@andrebrov
Сколько тестировщиков в вашей
команде?
Всегда кажется, что их не хватает
При этом...
• «У нас agile» - значит, тестирование
должно завершиться в том же спринте
• «Люблю короткие релизы»- значит
регрессионное тестирование надо делать
постоянно
• «Они опять изменили требования!» значит опять надо менять тесты
Хватит это терпеть!
Задачи
• Нужно иметь возможность проводить
регрессию в короткий период времени
• Тесты должны быть простыми, чтобы их
можно было легко
написать/дописать/переписать
• Поддержка тестов не должна занимать
много времени
Необходимые инструменты
• Тестовый фреймворк
• Фреймворк функционального тестирования
• CI Server
+ удобная IDE, понятный генератор отчетов,
удобный язык программирования...
Что взяли мы
•
•
•
•
•
•
TestNG
Selenium 2 / WebDriver
Spring
IntelliJ IDEA
Jenkins
Набор самописных утилит
Почему TestNG
•
•
•
•
Удобная работа с данными - @DataProvider
Разбиение тестов по группам
Многопоточность «из коробки»
«Фабрика» тестов
Почему WebDriver
•
•
•
•
Java-фреймворк
Абстракция на уровне PageObject
Работа с IE & FF
Активно развивается
Зачем Spring?
• Облегчение работы с базами данных
• Необходима интеграция с различными
сервисами в рамках тестов
• IoC
Этапы создания тестовой
платформы
Создание базового тестового
класса
public abstract class AbstractSeleniumTestClass extends AbstractTestNGSpringContextTests {
@Autowired
private WebDriver driver;
@BeforeMethod(alwaysRun = true)
public void printTestName(Method method) {
}
@AfterMethod(alwaysRun = true)
public void clearCookies(Method method) throws Exception {
}
protected WebDriver getWebDriver() {
}
public SearchPage loadLemAndLogin() {
}
}
Создание базовой webстраницы
public abstract class AbstractPage extends LoadableComponent<LoginPage> {
public AbstractPage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, DEFAULT_TIMEOUT);
PageFactory.initElements(driver, this);
}
protected abstract By getPageLoadedCheckElementLocator();
// Primitive actions
protected void clickOn(WebElement webElement) {
}
protected void type(WebElement webElement, String text) {
}
// Keys
protected void pressEnter(WebElement webElement) {
}
protected void pressRight(WebElement webElement) {
}
// Autocomplete
public void fillAutocomplete(WebElement webElement, String text) {
}
// Waits
public WebElement waitUntilFound(final By by) {
}
}
Описание web-страницы
dfpublic class LoginPage extends AbstractPage {
private static final Logger log = Logger.getLogger(LoginPage.class);
@FindBy(xpath = "//input[@name='USER']")
private WebElement usernameInput;
@FindBy(xpath = "//input[@name='PASSWORD']")
private WebElement passwordInput;
@FindBy(xpath = "//input[@class='Button']")
private WebElement loginButton;
public LoginPage(WebDriver driver) {
super(driver);
}
@Override
protected By getPageLoadedCheckElementLocator() {
}
@Override
protected void isLoaded() throws Error {
}
public SearchPage login() {
}
}
Вынесение данных в
DataProvider
public class SearchDataProvider {
@DataProvider
public static Object[][] searchTypes() {
Object[][] result = new Object[4][1];
result[0][0] = "BEGINS_WITH";
result[1][0] = "CONTAINS";
result[2][0] = "CONTAINS_SUBSTRING";
result[3][0] = "SOUNDS_LIKE";
return result;
}
}
Refactoring
• Вынесение текстовых констант из классов
страниц
• Группировка DataProvider`ов в классы
Подключение базы данных
<bean id=“dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
<property name="maxActive" value="10"/>
</bean>
<bean id="simpleJdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
<constructor-arg ref=“dataSource"/>
</bean>
Работа с базой внутри
DataProvider`ов
@Component
public class SearchByAlternateNameDataProvider {
private static DataProviderGenerator dataProviderGenerator;
@Autowired
public void setDataProviderGenerator(DataProviderGenerator dataProviderGenerator) {
SearchByAlternateNameDataProvider.dataProviderGenerator = dataProviderGenerator;
}
@DataProvider
public static Object[][] alternateNameAndNonSuitableCOI() {
return dataProviderGenerator.generatePairStringString("select …” + Config.DATA_COUNT);
}
}
@Component
public class DataProviderGenerator {
@Autowired
private TestingJdbcTemplate testingJdbcTemplate;
public Object[][] generatePairStringString(String sql) {
List<Pair> list = testingJdbcTemplate.getSimpleJdbcTemplate().query(sql, new PairRowMapper());
Object[][] result = new Object[list.size()][2];
int i = 0;
for (Pair pair : list) {
result[i][0] = pair.getOne().toString();
result[i++][1] = pair.getTwo().toString();
}
return result;
}
}
Хинт 1 – WebDriver как
SpringBean
@Configuration
public class SeleniumConfiguration {
@Autowired
private WebDriver driver;
public @Bean WebDriver driver() {
}
@PreDestroy
public void cleanUp() {
try {
driver.quit();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Хинт 2 – TestFactory для похожих
тестов
public class SearchTestFactory {
@Factory(dataProvider = "searchTypes", dataProviderClass = SearchDataProvider.class)
public Object[] createTest(String searchType) {
return new Object[]{new GenericSearchTest(searchType)};
}
}
public class GenericSearchTest extends AbstractSeleniumTest {
private String searchType;
public GenericSearchByLegalNameCOITest(String searchType) {
this.searchType = searchType;
}
@Test(dataProvider = "legalNamesAndCountries", dataProviderClass = SearchTestFactory.class)
@JiraIssue(number = “SRC-19")
public void test(String param1, String param2) {
}
}
Хинт 3 – Unit-тест как тест-кейс
SearchPage searchPage = loadAndLogin();
searchPage.setLegalNameSearchType(searchType);
searchPage.setLegalNameSearchParam(legalName);
SearchResultPage searchResultPage = searchPage.submit();
assertIsSortedByLegalName(searchResultPage);
Хинт 4 – Подключаем javascript
public void waitForAjaxComplete() {
log.verbose("waiting for ajax completion");
wait.until(new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver driver) {
return (Boolean) js.executeScript("return $.active == 0");
}
});
log.verbose("All ajax calls are complete");
}
Подключаем Jenkins
• Используем возможность запуска через
maven
• Подключаем отчеты от TestNG и видим
результаты регрессии
• Запуск тестов по расписанию / установке
новой версии / …
Profit!
Куда двигаться дальше
• Создание профилей тестирования (smokem
full, search)
• Selenium Grid и многопоточность
• 1 подход – разные типы приложений
(WebService, ETL, ...)
• End-to-end тестирование
Андрей Ребров
Arebrov@luxoft.com
@andrebrov
Download