Сейчас плотно занимаюсь темой тестирования 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 вложенный цикл.
сорри, непонятно написал.... проще или скриншотов сделать пачку или вебконференцию.
УдалитьМой емейл доступен. Пишите!
УдалитьСпасибо большое.
ОтветитьУдалить