Сейчас плотно занимаюсь темой тестирования BI проектов.
Как результат – появились интересные наработки, которыми хочу с вами поделиться.
Сегодня я покажу на примере как можно в автоматическом режиме тестировать BI отчетность, а именно – проверять все страницы всех информационных панелей на предмет наличия в них сообщений об ошибках.
Согласитесь, довольно полезный тест. Особенно если инфопанелей много, а предметных областей в репозитории BI мало.
По своему опыту могу сказать: при внесении изменений в RPD никогда точно не знаешь все ли отчеты остались в рабочем состоянии.
И приходится после каждой серьезной правки "прощелкивать" все информационные панели – все ли работает!
Это серьезная трата вашего времени!
(Я не рассматриваю вариантов, когда вам просто плевать работают отчеты или нет: "пользователи проверят")
Действия по проверке отчетов инфопанелей понятны, рутинны, и поэтому могут и должны быть автоматизированы!
Для создания теста мы воспользуемся замечательным (бесплатным) продуктом – Selenium.
В интернете очень много (в том числе на русском языке) инструкций по работе с Selenium IDE, Selenium RC.
Я не буду подробно останавливаться на том, как устроен Selenium, как создавать тесты – тема обширна. И достаточно хорошо освещена по ссылке выше.
Тут же я дам конкретные практические рекомендации как запустить определенный тест.
1. Для начала нам потребуется скачать jar-архивы с классами Selenium RC:
На странице http://seleniumhq.org/download/ в разделе "Selenium RC Server" доступна ссылка http://selenium.googlecode.com/files/selenium-server-standalone-2.25.0.jar
Также в разделе "Selenium Client Drivers" доступна ссылка на скачивание клиентской библиотеки для Java-проектов http://selenium.googlecode.com/files/selenium-java-2.25.0.zip
Сохраним эти файлы, например, в C:/Selenium
2. В папке C:/Selenium создадим bat-файл start-selenium-server.bat со следующим содержимым:
java -jar selenium-server-standalone-2.25.0.jar
Запустим bat-файл.
Тем самым мы получим работающий сервер Selenium'а, который будет "прощелкивать" в браузере страницы ваших информационных панелей.
3. Теперь пора создать сам тест – мы будем писать его на языке Java.
Запустите Eclipse (Java IDE).
Если у вас до сих пор его нет – скачайте http://www.eclipse.org/downloads/ (либо, если вы пользуетесь другой средой, создавайте проект по аналогии с описываемым далее).
3.1) Создайте новый Java-проекта с именем SeleniumTest
3.2) В нем определите package, например, у меня это – ru.servplus.tests
3.3) внутри него создайте новый JUnit Test Case
Укажите тип JUnit 4, задайте имя классу теста – CheckAllDashboardPages, и щелкните чекбоксы для создания методов setUp() и tearDown().
3.4) содержимое созданного класса возьмите отсюда:
package ru.servplus.tests;
import java.io.FileInputStream;
import java.util.Properties;
import java.util.ArrayList;
import java.util.Iterator;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.SeleneseTestBase;
import com.thoughtworks.selenium.Selenium;
public class CheckAllDashboardPages extends SeleneseTestBase {
private Selenium selenium;
private Properties prop;
private String serverHost;
private int serverPort;
private String browserType;
private String browserUrl;
private String nqUser;
private String nqPassword;
@Before
public void setUp() throws Exception {
prop = new Properties();
prop.load(new FileInputStream("selenium.properties"));
serverHost = prop.getProperty("serverHost");
serverPort = Integer.valueOf(prop.getProperty("serverPort"));
browserType = prop.getProperty("browserType");
browserUrl = prop.getProperty("browserUrl");
nqUser = prop.getProperty("nqUser");
nqPassword = prop.getProperty("nqPassword");
selenium = new DefaultSelenium(
serverHost,
serverPort,
browserType,
browserUrl);
selenium.start();
}
@After
public void tearDown() {
selenium.stop();
}
public String getDashboardPath (int i) {
StringBuilder script = new StringBuilder();
script.append("var div1 = window.document.getElementById('idCatalogItemsAccordion');");
script.append("var div2 = div1.getElementsByTagName('DIV')[0];");
script.append("var div3 = div2.getElementsByTagName('DIV')[1];");
script.append("var tab1 = div3.getElementsByTagName('TABLE')[0];");
script.append("var tbd1 = tab1.getElementsByTagName('TBODY')[0];");
script.append("var tr1 = tbd1.childNodes[" + i + "];");
script.append("var td1 = tr1.getElementsByTagName('TD')[0];");
script.append("var div4 = td1.getElementsByTagName('DIV')[0];");
script.append("var trows = div4.getElementsByTagName('TR');");
script.append("var tr2 = trows[trows.length-3];");
script.append("var td2 = tr2.getElementsByTagName('TD')[0];");
script.append("var a1 = td2.getElementsByTagName('A')[0];");
script.append("a1.listItem.itemInfo.path.toString();");
String path = selenium.getEval(script.toString());
return path;
}
@Test
public void test() {
//входим в BI под админ. учетной записью (имеет смысл создать отдельную учетку-reader)
//также крайне важно задать язык сессии, так как по англ. заголовкам элементов будет вестись поиск в тесте
selenium.open("/analytics/saw.dll?catalog&nqUser=" + nqUser + "&nqPassword=" + nqPassword + "&lang=en");
//перейдем в режим "Расширенного поиска": будем искать все инфопанели в shared-папках
selenium.open("/analytics/saw.dll?catalog&action=searchpanel&type=all#{\"action\":\"search\",\"location\":\"/shared\",\"mask\":\"*\",\"type\":\"dashboard\",\"favorite\":null}");
int dashboardCount = 0;
//крайне важно дождаться появления указанных XPath-конструкцией элементов (они догружаются ajax'ом после того как загрузилась сама страница)
selenium.waitForCondition("selenium.getXpathCount(\"//div[@id='idCatalogItemsAccordion']/div/div[2]/table/tbody/tr\") > 0", "30000");
//считываем кол-во строк, возвращенных страницей поиска инфопанелей
dashboardCount = selenium.getXpathCount("//div[@id='idCatalogItemsAccordion']/div/div[2]/table/tbody/tr").intValue();
//System.out.println("Dashboard count: "+dashboardCount);
//определяем массив инфопанелей (путь + название) и массив страниц каждой инфопанели
String[] dashboardPaths = new String[dashboardCount];
String[] dashboardPathsEncoded = new String[dashboardCount];
String[][] dashboardPages = new String[dashboardCount][];
//заполняем значениями эти массивы (считываем путь и название каждой инфопанели с помощью XPath-конструкций)
for(int i=0; i<dashboardCount; i++){
/*dashboardPaths[i] = selenium.getText("//div[@id='idCatalogItemsAccordion']/div/div[2]/table/tbody/tr[" + (i+1) + "]/td/div/table/tbody/tr[2]/td/a") +
"/" + selenium.getText("//div[@id='idCatalogItemsAccordion']/div/div[2]/table/tbody/tr[" + (i+1) + "]/td/div/table/tbody/tr[1]/td[2]/span[1]");
dashboardPaths[i] = dashboardPaths[i].replace("/Shared Folders/", "/shared/").replace("/Dashboards/", "/_portal/");
*/
dashboardPaths[i] = getDashboardPath(i);
dashboardPathsEncoded[i] = dashboardPaths[i].replace("\\", "\\\\");
dashboardPathsEncoded[i] = dashboardPathsEncoded[i].replace("\"", "\\\"");
//System.out.println(dashboardPathsEncoded[i]);
}
//далее будем считывать значения названий страниц каждой инфопанели
int dashboardPageCount = 0;
for(int i=0; i<dashboardCount; i++){
//не получилось реализовать считывание страниц внутри одного и того же окна браузера
//возможно, из-за хитрого параметра "type"
//сейчас реализовано таким образом, что список страниц каждой инфопанели открывается в новом окне
//открываем в новом окне страницу со списком содержимого инфопанели
selenium.openWindow("/analytics/saw.dll?catalog&action=searchpanel&type=all#{\"location\":\"" + dashboardPathsEncoded[i] + "\"}", dashboardPaths[i]);
selenium.waitForPopUp(dashboardPaths[i], "30000");
//перемещаем фокус в новое окно
selenium.selectWindow(dashboardPaths[i]);
//на всякий случай ставим задержку - даем странице успеть отрисоваться
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//убеждаемся, что на странице появились данные о страницах инфопанели
selenium.waitForCondition("selenium.getXpathCount(\"//div[@id='idCatalogItemsAccordion']/div/div[2]/table/tbody/tr\") > 0", "10000");
//считываем кол-во страниц текущей инфопанели
dashboardPageCount = selenium.getXpathCount("//div[@id='idCatalogItemsAccordion']/div/div[2]/table/tbody/tr").intValue();
ArrayList<String> dashboardPageNames = new ArrayList<String>();
//в цикле считываем наименования страниц инфопанели с помощью Xpath-конструкции
String dashboardPageName = null;
for(int j=0; j<dashboardPageCount; j++){
dashboardPageName = selenium.getText("//div[@id='idCatalogItemsAccordion']/div/div[2]/table/tbody/tr[" + (j+1) + "]/td/div/table/tbody/tr[1]/td[2]/span[1]");
if (!dashboardPageName.equalsIgnoreCase("_selections") && !dashboardPageName.equalsIgnoreCase("dashboard layout")) {
dashboardPageNames.add(dashboardPageName);
}
}
//закрываем вспомогательное окно
selenium.close();
//перемещаем фокус в главное окно браузера
selenium.selectWindow("null");
dashboardPages[i] = (String[])dashboardPageNames.toArray(new String[dashboardPageNames.size()]);
}
int errorCount = 0;
int allErrorCount = 0;
String errorText = null;
String dashboardPageEncoded = null;
//в цикле по всем инфопанелям
for(int i=0; i<dashboardCount; i++){
//в цикле по всем страницам текущей инфопанели
for(int j=0; j<dashboardPages[i].length; j++){
dashboardPageEncoded = dashboardPages[i][j].replace("\\", "\\\\").replace("\"", "\\\"");
//открываем страницу инфопанели текущей итерации
selenium.open("/analytics/saw.dll?Dashboard&PortalPath="+dashboardPathsEncoded[i]+"&page="+dashboardPageEncoded);
//даем странице время отрисоваться
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//убеждаемся что все "часики" отработали, на всякий случай помещаем в try-блок - если за 30 сек. отчет не отработает
try {
selenium.waitForCondition("selenium.getXpathCount(\"//div[contains(text(),'Searching... To cancel, click')]\") == 0", "30000");
} catch (Exception e) {
e.printStackTrace();
}
//считываем кол-во ошибок (Xpath-проверка по наличию элементов с текстом 'Error Details')
errorCount = selenium.getXpathCount("//a[contains(text(),'Error Details')]").intValue();
allErrorCount = allErrorCount + errorCount;
//Выводим информацию о проблемной странице инфопанели в System.out
if (errorCount > 0) {
System.out.println("DASHBOARD: " + dashboardPaths[i]);
System.out.println("DASHBOARD PAGE: " + dashboardPages[i][j]);
System.out.println("ERRORS COUNT: " + errorCount);
for(int k=0; k<errorCount; k++){
errorText = selenium.getText("xpath=(//a[contains(text(),'Error Details')]/parent::div/div/div[2]/div)[" + (k+1) + "]");
System.out.println("error["+k+"]: " + errorText);
}
System.out.println("========================================================================================================");
}
}
}
//делаем logout текущей BI-сессии
selenium.click("//span[@id='logout']/span/span");
//закрываем главное окно браузера
selenium.close();
//если в ходе теста были обнаружены проблемные страница инфопанелей - тест считаем проваленным
assertTrue("See System.out for error explanation", (allErrorCount == 0) );
}
}
3.5) далее следует прописать пути до некоторых библиотек
И указать, что вы хотите включить в проект jar-файлы, полученные на 1-м шаге.
3.6) В тексте класса встречается обращение к файлу selenium.properties – в нем я храню все константы, настраиваемые параметры.
Поэтому создадим файл selenium.properties в корне проекта.
Содержимое файла:
serverHost=localhost serverPort=4444 browserType=*firefox browserUrl=http://localhost:7001 nqUser=weblogic nqPassword=weblogic_1Исправьте значения параметров browserUrl (хост и порт вашего BI сервера), nqUser, nqPassword под вашу среду.
3.7) На этом можно было бы и остановиться.
Уже теперь, запустив ваш класс теста, вы получите автоматизированную проверку ваших информационных панелей.
Суть теста в эмуляции (благодаря Selenium RC Server, который по умолчанию "слушает" на localhost:4444 входящие запросы) работы браузера.
Нашим java-кодом мы описываем команды firefox-браузеру (browserType-параметр проперти-файла).
Эти команды включают в себя:
- открытие страницы расширенного поиска BIEE
- поиск всех информационныех панелей и сохранение путей до них
- считывание всех страниц каждой инфопанели
- последовательное открытие каждой страницы инфопанелей и поиск сообщений об ошибках.
По окончанию теста, в System.out будут записаны страницы инфопанелей, которые содержат ошибки, и сами тексты ошибок.
(На всякий случай, выкладываю архив java-проекта.
В нем, помимо основного теста, находится класс CheckAllDashboardPagesWoCache, который не просто открывает страницы инфопанелей, но еще и рандомно меняет значения обязательных параметров).
4. Лично меня напрягает для каждого теста стартовать Eclipse.
Потому пошел чуть дальше – для запуска тестов стал использовать среду "непрерывной интеграции" Jenkins (опять же бесплатную).
И опять прошу меня простить, но тема эта очень обширна, и углубляться в описание этого продукта не буду. В интернете информации очень много.
4.1) Скачаем jenkins.war по ссылке.
Запустим его командой
java -jar jenkins.war
Далее вы можете установить его как сервис в разделе "Manage Jenkins"
4.2) Теперь нам потребуется Apache Ant. Если у вас его нет – скачиваем с сайта http://ant.apache.org/bindownload.cgi
Например, http://www.sai.msu.su/apache//ant/binaries/apache-ant-1.8.4-bin.zip
Распаковываем архив, например, в "c:\Program Files\Ant\apache-ant-1.8.4"
В разделе Manage Jenkins выбираем Configure System и прописываем путь до директории с Ant
4.3) Ant нам был нужен для запуска теста и получения его результатов извне, т.е. из Jenkins. Но чтобы этого добиться – следует в нашем исходном java-проекте в корне создать еще один файл – build.xml.
Он будет содержать инструкции для Ant.
В нашем случае:
То есть задача Ant-скрипта состоит в очистке папки со скомпилированными классами, папки с результатом выполнения теста;
затем компиляция классов и запуск JUnit-теста (наш класс является JUnit4 тестом) с выводом результатов в XML-файл.
4.3) На главной странице Дженкинс выбираем пункт New Job
И создаем "a free-style software project".
Дадим ему имя - REGR_CheckAllDashboardPages
В домашней директории Jenkins/jobs должна появиться папка REGR_CheckAllDashboardPages/workspace.
Копируем в нее содержимое нашего проекта:
Теперь следует сконфигурировать проект в Jenkins таким образом, чтобы он знал какой экземпляр Ant использовать (у меня это Ant184, настроенный на уровне всей системы) и какую цель (target) следует Ant запускать (у нас это test) в ходе сборки-build'а (все-таки это система непрерывной интеграции и тут все крутится вокруг билдов).
Также добавим шаг пост-обработки билда, а именно – публикацию для каждого билда результатов выполнения теста.
И последний штрих – следует скопировать в lib-директорию Ant'а все новые jar-файлы Selenium'а и jar-файл JUnit4
4.4) И теперь вы можете как разово запускать новые сборки билдов проекта, так и "зашедулить" их.
В результате у вас будет храниться история сборок билдов, где каждый будет содержать отчет о выполнении Selenium-теста.
P.S. Наверное, для больших, распределенных команд разработки под OBIEE будет полезно применять Jenkins полностью – то есть задействовать все шаги непрерывной интеграции:
- По коммиту в SVN/GIT запускать сборку билда.
- Получать из SVN последние изменения.
- Применять эти изменения на некотором тестовом сервере BI (с помощью вызова shell/bat-файлов для копирования RPD-файлов, веб-каталога; с помощью WLST для остановки и запуска BI компонентов и т.д.).
- Запускать «прогоны» всех регрессионных тестов (согласитесь, наш сегодняшний тест - регресионный).
- По результату выполнения тестовы отсылать письма-уведомления всем участникам проекта с перечнем ошибок.
Выглядит интересно…
P.P.S. В следующий раз расскажу как организовать нагрузочное тестирование вашего BI сервера.
Спасибо! )
ОтветитьУдалитьYou're welcome!
ОтветитьУдалитьБлагодаря одному из читателей выяснилось, что приведенное решение "спотыкается" в случае:
ОтветитьУдалить- наличия скрытых инфопанелей;
- наличия описаний для инфопанелей;
- наличия спецсимволов("/") в названии инфопанели.
В ближайшие дни внесу исправления.
Отличная идея! На какой версии BI это прогонялось? У меня на 11.1.1.5 летит эксепшен
ОтветитьУдалитьcom.thoughtworks.selenium.SeleniumException: ERROR: Element //div[@id='idCatalogItemsAccordion']/div/div[2]/table/tbody/tr[29]/td/div/table/tbody/tr[2]/td/a not found
at com.thoughtworks.selenium.HttpCommandProcessor.throwAssertionFailureExceptionOrError(HttpCommandProcessor.java:112)
at com.thoughtworks.selenium.HttpCommandProcessor.doCommand(HttpCommandProcessor.java:106)
at com.thoughtworks.selenium.HttpCommandProcessor.getString(HttpCommandProcessor.java:275)
Роман, работает на версии 11.1.1.6.2.
ОтветитьУдалитьУ коллег с 11.1.1.5 тоже вылетает ошибка.
Но как выяснилось, ошибка из-за наличия описаний для инфопанелей (в момент определения списка инфопанелей). С предновогодней суетой руки не доходят подправить... В начале января обязательно что-нибудь придумаю!
Готов всячески помогать и тестировать! С наступающим НГ!!!
ОтветитьУдалитьРоман, спасибо за поздравления!!! ;)
ОтветитьУдалитьВнес изменения в java-код (как в тексте поста, так и в архиве проекта по ссылке).
Изменения учитывают наличие спец.символов (двойные кавычки, обратные слеши) в названиях инфопанелей/страниц инфопанелей; наличие описания для инфопанелей/страниц.
Суть изменений в том, что для определения абсолютного пути инфопанели в структуре веб-каталога используется вызов функции getDashboardPath, которая в свою очередь с помощью getEval обращается к javascript-функции.
Также добавлена явная обработки символов обратного слеша и двойных кавычек в названиях инфопанелей/страниц.
Спасибо! Попробовал новую версию, не отрабатывает поиск dashboards, т.е. открывается диалог поиска с
Удалитьпустыми полями и дальше вылет по таймауту. Если же не дожидаться таймаута, а ввести данные руками и нажать search, то дальше все работает замечательно...
Роман, возможно, вы пытаетесь выполнить тесты под какой-нибудь совсем "кастрированной" учетной записью...
УдалитьПопробуйте войти в BI и открыть затем URL:
http://YOUR_SERVER_HOST:PORT/analytics/saw.dll?catalog&action=searchpanel&type=all#{"action":"search","location":"/shared","mask":"*","type":"dashboard","favorite":null}
Из браузера под тем же логином отрабатывает нормально,но все равно придется "курить" XPath, т.к. у нас каждый dashboard имеет кучу закладок и запрос
Удалитьselenium.openWindow("/analytics/saw.dll?catalog&action=searchpanel&type=all#{\"location\":\"" + dashboardPathsEncoded[i] + "\"}", dashboardPaths[i]);
просто открывает их список. Получается, что надо делать еще 1 вложенный цикл.
сорри, непонятно написал.... проще или скриншотов сделать пачку или вебконференцию.
УдалитьМой емейл доступен. Пишите!
УдалитьСпасибо большое.
ОтветитьУдалить