Часть 2
Миф о том, что для автоматизированного тестирования не нужны навыки программирования и тесты можно вполне легко записывать, развенчан настолько, что сам факт того, что кто-то еще так считает, уже кажется мифом или, как минимум, чем-то странным. Понятное дело, что записанные тесты и тесты, которые могли бы эффективно использоваться на регулярной основе – это, как говорят в Одессе, «две большие разницы». Очевидно, что исходный тест, да и решение по автоматизации в целом, должны пройти некоторые стадии улучшения, прежде чем прийти к пригодному к использованию виду. Давайте рассмотрим некоторые типовые шаги, которые так или иначе приходится проходить для построения стабильного, расширяемого и поддерживаемого решения.
И рассмотрим мы эти шаги на примере Selenium-RC (Java). На самом деле, данная тематика не сильно завязана на конкретный инструментарий. Всё то же самое можно сделать практически любым средством, которое в себе содержит возможность программирования тестов. Просто чтобы не уходить в абстрактную теорию, а показать на конкретных примерах, как оно работает, нужно привязаться к какой-то конкретно реализации. Но подобные практики имеют место быть в любых аналогичных решениях. Поэтому, тут можно усматривать не только особенность работы с каким-то конкретным средством, но и стоит обратить внимание на практики в целом, которые будут задействованы.
Итак, путем записи и незначительной корректировки был получен следующий тест:
``` package com.mycompany.selenium.tests; import java.util.Date; import com.thoughtworks.selenium.*; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test;
/**
-
@author KaNoN
*/
public class SampleTestStage00 {
private Selenium selenium = null;
private String genTaskName() {
Date dt = new Date();
String result = dt.toString();
result = result.replaceAll( “:” , “” );
result = result.replaceAll( " " , “” );
return result;
}
@BeforeMethod(alwaysRun=true)
public void init() throws Exception {
selenium = new DefaultSelenium( "localhost" , 4444 , "*iexplore" , "http:// localhost:8080" );
selenium.start();
selenium.open("http://localhost:8080/hudson");
}
@Test(groups = {"sample","sample0"})
public void testCreateJob() throws Exception {
selenium.click("link=New Job");
selenium.waitForPageToLoad("30000");
selenium.type("name", "SampleTask" + genTaskName() );
selenium.click("//input[@value='Build a free-style software project']");
selenium.click("//button[@type='button']");
selenium.waitForPageToLoad("30000");
selenium.click("//button[text()='Save']");
selenium.waitForPageToLoad("30000");
selenium.click("link=Hudson");
selenium.waitForPageToLoad("30000");
}
@AfterMethod(alwaysRun=true)
public void stop() throws Exception {
selenium.stop();
}
}
</p><p>Что он делает? Он запускает веб-приложение (в данном примере это <a href="http://automated-testing.info/news/vybor-sredstv-nepreryvnoj-integracii-continuous-integration" target="_blank" title="Выбор средств непрерывной интеграции">Hudson</a>) и создает в нем новую задачу. Тест реализован с использованием <strong>TestNG </strong> в качестве тестового движка.</p><p>Как видно, тест еще далек от совершенства, как минимум из-за того, что много hard-coded значений. Да и вот так на первый взгляд трудно понять, что этот тест детает вообще. В-общем, непонятных вещей много, нужно усовершенствовать. Вопрос в том, что, когда и (самое главное) как.<!--break--></p><h2>Шаг 1: Вынесение величин, специфических для окружения в конфигурационный файл</h2><p>Стоит мне вот этот тест запустить с другой машины, врядли я могу расчитывать на то, что он выдаст мне тот же результат. Хотя бы потому, что на этой машине скорее всего не окажется локально установленного приложения. И не факт, что сам <a href="http://automated-testing.info/knowledgebase/lesson/selenium-server-v-interaktivnom-rezhime" target="_blank" title="Selenium Server">сервер Selenium-a</a> будет запущен локально и на том же порту. И наконец, я бы не хотел править все тесты, если я захочу запустить их под другим браузером. Для этого мне нужно сделать изменение максимум в одном файле.</p><p>Для таких задач существуют конфигурационные файлы и соответственно объекты, которые могли бы хранить эти данные. В Java для таких задач имеются *.properties файлы и соответствующие стандартные средства для работы с ними. В других языках программирования всё аналогично. Например, для Ruby используется YAML-формат. Опять же, никто не отменял те же ini-файлы. В крайнем случае, всегда можно написать свой считыватель конфигурационных данных.</p><p>Итак, нам нужно где-то задать конфигурацию и создать класс, который бы предоставлял возможность, как считывания, так и возвращения значений параметров конфигурации. В корневом каталоге проекта создаем отдельный каталог <strong>config</strong>, в нем создадим файл <strong>env.properties</strong> (можно любое другое имя, лишь бы было понятно, что там находится) и добавим туда наши параметры конфигурации. Получится что-то вида:</p><p>
host=localhost
port=4444
url=http://localhost:8080
browser=*iexplore
delay=30000
</p><p>Здесь мы задали параметры для <a href="http://automated-testing.info/knowledgebase/article/opisanie-ispolzovanie-i-opcii-zapuska-selenium-server" target="_blank" title="Запуск и опции Selenium Server">запуска Selenium-а</a>, а также время ожидания, которое достаточно интенсивно используется в методе <a href="http://automated-testing.info/knowledgebase/article/selenium-rc-obespechenie-krossbrauzernosti-testov" target="_blank" title="Как использовать waitForPageToLoad">waitForPageToLoad в предыдущем примере</a>. Теперь нам надо это считать. Для этого мы создадим отдельный класс <strong>Config</strong>, который содержал бы в себе следующий функционал:</p><ol><li>Загрузка всех *.properties файлов из заданного каталога и всех подкаталогов произвольной вложенности</li><li>Извлечение значения свойства по его имени</li><li>Объект данного класса должен быть один в системе и должен самостоятельно обеспечить инициализацию свойств без явного указания на данную операцию</li></ol><p>А теперь всё то же самое, но пошагово и с примерами.</p><p> Для загрузки всех свойств нам нужен метод, которому надо указать каталог. Причем, метод должен вызываться рекурсивно (у нас произвольный уровень вложенности каталогов). Изначально получаем что-то наподобие:</p><p>
package com.mycompany.selenium.lib;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
-
@author KaNoN
*/
public class Config {
private Properties prop = null;
FilenameFilter filter = null;
FilenameFilter dirFilter = null;
public void loadProperties( String path ) throws Exception {
File dir = new File( path );
filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(“.properties”); } };
dirFilter = new FilenameFilter() { public boolean accept(File dir, String name) { return dir.isDirectory(); } };
String files[] = dir.list(filter);
if( prop == null ){
prop = new Properties();
}
for( String file:files ){
System.out.println( file );
File localFile = new File( “config\” + file );
if( localFile.isDirectory() ) continue;
FileInputStream fis = new FileInputStream( localFile.getAbsolutePath() );
prop.load( fis );
fis.close();
}
String dirs[] = dir.list(dirFilter);
for( String directory:dirs ){
loadProperties( path + “\” + directory );
}
}
}
</p><p>Теперь осталось выполнить второй шаг, так как это собственно один из основных функционалов, который используется при написании тестов. Собственно:</p><p>
package com.mycompany.selenium.lib;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
-
@author KaNoN
*/
public class Config {
private Properties prop = null;
FilenameFilter filter = null;
FilenameFilter dirFilter = null;public void loadProperties( String path ) throws Exception {
File dir = new File( path );
filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(“.properties”); } };
dirFilter = new FilenameFilter() { public boolean accept(File dir, String name) { return dir.isDirectory(); } };
String files[] = dir.list(filter);if( prop == null ){ prop = new Properties(); } for( String file:files ){ System.out.println( file ); File localFile = new File( "config\\" + file ); if( localFile.isDirectory() ) continue; FileInputStream fis = new FileInputStream( localFile.getAbsolutePath() ); prop.load( fis ); fis.close(); } String dirs[] = dir.list(dirFilter); for( String directory:dirs ){ loadProperties( path + "\\" + directory ); }
}
public String getProperty( String propertyName ) throws Exception {
return prop.getProperty( propertyName );
}
}
</p><p>Теперь мы можем уже использовать данный класс в тестах. Для этого где-то вначале можно сделать вызов</p><p>
Config conf = new Config();
conf.loadProperties( “config” );
</p><p>А затем везде, где нужно использовать значение некоторого свойства, подставляем</p><p>
conf.getProperty( “some_property” );
</p><p>Но теперь представьте, что такую инициализацию и использование локальной переменной надо осуществлять во всех тестах. Как минимум это избыточные строки. Хотелось бы как-то от этого избавиться. К тому же, данный объект обычно нужен тестам в единичном экземпляре. Поэтому, подредактируем данный класс так:</p><p>
package com.mycompany.selenium.lib;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
-
@author KaNoN
*/
public class Config {
private static Config instance = null;
private Properties prop = null;
FilenameFilter filter = null;
FilenameFilter dirFilter = null;private void loadProperties( String path ) throws Exception {
File dir = new File( path );
filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(“.properties”); } };
dirFilter = new FilenameFilter() { public boolean accept(File dir, String name) { return dir.isDirectory(); } };
String files[] = dir.list(filter);if( prop == null ){ prop = new Properties(); } for( String file:files ){ System.out.println( file ); File localFile = new File( "config\\" + file ); if( localFile.isDirectory() ) continue; FileInputStream fis = new FileInputStream( localFile.getAbsolutePath() ); prop.load( fis ); fis.close(); } String dirs[] = dir.list(dirFilter); for( String directory:dirs ){ loadProperties( path + "\\" + directory ); }
}
private Config() throws Exception {
loadProperties( “config” );
}public static String getProperty( String propertyName ) throws Exception {
if( instance == null ){
instance = new Config();
}
return instance.prop.getProperty( propertyName );
}
}
</p><p>Как видно из изменений, у нас есть только один доступный метод <strong>getProperty</strong>, который теперь статический. Более того, конструктор объявлен как <strong>private</strong>. То есть, мы уже не сможем сделать что-то наподобие:</p><p>
Config conf = new Config();
</p><p>за пределами текущего класса. Но при этом обратите внимание на метод <strong>getProperty</strong>, а точнее на изменения, которые были внесены.</p><p> Во-первых, данный класс сейчас содержит еще одно поле <strong>instance</strong>, которое является экземпляром данного класса. Приватный конструктор по-прежнему позволяет создавать экземпляры класса в пределах текущего класса. Во-вторых, поле <strong>instance </strong>инициализируется один раз за время работы тестов. То есть, в тесте мы можем сделать вызов вида:</p><p>
Config.getProperty( “some_property” );
</p><p> При этом нас не должно беспокоить, загружены ли свойства или нет. Об этом заботится сам класс. В итоге, когда мы реализовали подобный класс, мы можем переписать наш тест уже в таком виде: </p><p>
package com.mycompany.selenium.tests;
import java.util.Date;
import com.mycompany.selenium.lib.Config;
import com.thoughtworks.selenium.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.Assert;
/**
-
@author KaNoN
*/
public class SampleTestStage01 {
private Selenium selenium = null;
private String delay = “”;private String genTaskName() {
Date dt = new Date();
String result = dt.toString();
result = result.replaceAll( “:” , “” );
result = result.replaceAll( " " , “” );
return result;
}@BeforeMethod(alwaysRun=true)
public void init() throws Exception {
delay = Config.getProperty( “delay” );
selenium = new DefaultSelenium( Config.getProperty( “host” ) , new Integer( Config.getProperty( “port” ) ) , Config.getProperty( “browser” ) , Config.getProperty( “url” ) );
selenium.start();
selenium.open( Config.getProperty( “url” ) + “/hudson/”);
}@Test(groups = {“sample”,“sample1”})
public void testCreateJob() throws Exception {
selenium.click(“link=New Job”);
selenium.waitForPageToLoad(delay);
selenium.type(“name”, “SampleTask” + genTaskName() );
selenium.click(“//input[@value=‘Build a free-style software project’]”);
selenium.click(“//button[@type=‘button’]”);
selenium.waitForPageToLoad(delay);
selenium.click(“//button[text()=‘Save’]”);
selenium.waitForPageToLoad(delay);
selenium.click(“link=Hudson”);
selenium.waitForPageToLoad(delay);
}@AfterMethod(alwaysRun=true)
public void stop() throws Exception {
selenium.stop();
}
}
</p><p>Что нового здесь?</p><ol><li>Вместо явно заданного времени ожидания была зарезервирована переменная <strong>delay</strong>, которая в начале теста инициализируется значением из файла конфигурации</li><li> Все явные указания хостов, портов и базового адреса тестируемого приложения уже получаются через обращение к классу <strong>Config</strong>.</li></ol><p>В результате, если мы хотим, чтобы наши тесты работали с разных машин, то при их переносе достаточно просто подкорректировать конйигурационный файл.</p><h2>Шаг 2: Вынесение общих частей тестовых классов в некий базовый класс</h2><p>Теперь представим, что мы создаем еще один тест. А также представим, что в нем будет присутствовать тоже, что и в предыдущем тесте, учитывая, что работаем мы с тем же приложением?</p><ol><li>Во-первых, это будет инициализация <a href="http://automated-testing.info/knowledgebase/article/chto-takoe-selenium-i-s-chem-ego-edjat" target="_blank" title="Что такое Selenium и с чем его едят?">Selenium</a>-а, да и собственно сама переменная, которая содержит в себе Selenium client driver.</li><li>Во-вторых, это будет переменная <strong>delay</strong>, которую полезно бы использовать во всех тестах.</li><li>В-третьих, это будет вызов метода, который завершает работу <a href="http://automated-testing.info/knowledgebase/article/chto-takoe-selenium-i-s-chem-ego-edjat" target="_blank" title="Что такое Selenium и с чем его едят?">Selenium</a>-a.</li></ol><p>Создадим некоторый базовый класс для тестов и пусть наш тестовый класс будет наследником от этого базового класса. Итак, базовый класс:</p><p>
package com.mycompany.selenium.lib;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
/**
-
@author KaNoN
*/
public class BaseTestClass extends SecurityManager {
protected Selenium selenium = null;
protected String delay = “”;@BeforeMethod(alwaysRun=true)
public void init() throws Exception {
delay = Config.getProperty( “delay” );
selenium = new ExtendedSelenium( Config.getProperty( “host” ) , new Integer( Config.getProperty( “port” ) ) , Config.getProperty( “browser” ) , Config.getProperty( “url” ) );
selenium.start();
selenium.open( Config.getProperty( “url” ) + “/hudson” );
mainPage = new MainPage( selenium );
}@AfterMethod(lastTimeOnly=true)
public void stop() throws Exception {
selenium.stop();
}
}
</p><p>Если мы наш тест отнаследуем от данного базового класса, то у нас код упростится до такого вида:</p><p>
package com.mycompany.selenium.tests;
import com.mycompany.selenium.lib.BaseTestClass;
import java.util.Date;
import org.testng.annotations.Test;
/**
-
@author KaNoN
*/
public class SampleTestStage02 extends BaseTestClass {
private String genTaskName() {
Date dt = new Date();
String result = dt.toString();
result = result.replaceAll( “:” , “” );
result = result.replaceAll( " " , “” );
return result;
}
@Test(groups = {"sample","sample2"})
public void testCreateJob() throws Exception {
this.selenium.click("link=New Job");
selenium.waitForPageToLoad(delay);
selenium.type("name", "SampleTask" + genTaskName() );
selenium.click("//input[@value='Build a free-style software project']");
selenium.click("//button[@type='button']");
selenium.waitForPageToLoad(delay);
selenium.click("//button[text()='Save']");
selenium.waitForPageToLoad(delay);
selenium.click("link=Hudson");
selenium.waitForPageToLoad(delay);
}
}
</p><p>То есть, теперь мы оперируем уже только с кодом, который специфичен именно для данного теста.</p><h2>Шаг 3: Маппинг локаторов и расширение используемых библиотек</h2><p>Поскольку наш тест работает на уровне ГУИ, то его работоспособность чувствительна к изменениям элементов страницы. Наиболее часто меняются сами <a href="http://automated-testing.info/knowledgebase/article/ispolzovanie-lokatorov-locators-v-selenium-chast-1" target="_blank" title="Локаторы Selenium">локаторы</a>. И если один и тот же локатор используется в разных тестах и по многу раз, то при изменении соответствующего элемента придется править все тесты, которые были завязаны на использование данного уже неактуального локатора. А что будет, если таких элементов будет множество? Даже представить страшно.</p><p>Другой момент – удобство понимания того, над каким элементом производится действие. Если еще первый клик по ссылке и ввод текста в данном тесте как-то можно увязать с некоторой страницей, на которой Selenium находится на момент выполнения, то конструкция вида:</p><p>
selenium.click(“//button[@type=‘button’]”);
</p><p>может ввести в лугкий ступор. И наконец, просто неудобно постоянно вставлять подобную конструкцию: </p><p>
selenium.waitForPageToLoad(delay);
</p><p>Особенно это неудобно, если подобная конструкция используется достаточно часто, причем в комбинации с некоторым фиксированным набором команд. В нашем случае, удобно было бы иметь разновидность метода <strong>click</strong>, которая бы еще и ждала загрузки страницы. И под шумок можно добавить еще ряд полезного функционала, которого изначально у Selenium-а нет.</p><p> И <a href="http://automated-testing.info/knowledgebase/article/ispolzovanie-mappinga-polzovatelskogo-interfejsa-v-selenium-rc" target="_blank" title="Маппинг и UI map в Selenium">маппинг</a> и расширение Selenium-а рассматриваются в одном контексте, так как это взаимнодополняющие друг друга решения. Например, мне бы хотелось, чтобы я мог передать в метод строку и если она соответствует некоторому псевдониму элемента, то этот псевдоним подхватился бы и подставился фактический локатор. А если такого псевдонима не нашлось, то данная строка бы использовалась как явно заданный локатор.</p><p>А теперь решим эту задачу последовательно. Для маппинга нам вполне могут пригодиться те же <strong>*.properties</strong> файлы. Хотя бы потому, что по сути нам нужно некоторое представление пар: <strong>«псевдоним-локатор»,</strong> что технически эквивалентно паре <strong>«свойство-значение»</strong>. И к тому же, мы уже описали класс, который мы можем использовать для данной задачи.</p><p> В том же каталоге config, что мы использовали для хранения конфигурационных файлов, мы можем добавить подкаталог <strong>maps</strong>, в которых будут храниться карты элементов. Поскольку псевдоним можно задавать произвольно, для удобства их использования можно выработать некоторые правила их именования, например: <strong><page_name>.<element_name></strong></p><p>То есть, мы указываем не только что это за элемент, но и к какой странице он относится. Дальше можно добавить различные соглашения по именованию страниц, элементов определенного типа и т.д. Это уже особенности конкретных проектов.</p><p> Для примера добавим в каталог <strong>config/maps</strong> файл <strong>mainpage.properties</strong> со следующим содержимым:</p><p>
leftpanel.newjob=link=New Job
leftpanel.hudson=link=Hudson
newjobpage.name=name
newjobpage.freestyleradio=//input[@value=‘Build a free-style software project’]
newjobpage.ok=//button[@type=‘button’]
configurejob.save=//button[text()=‘Save’]
</p><p>Полей пока немного, но по мере разрастания имеет смысл разделять map-файлы по модулям, более мелким блокам, когда количество элементов маппинга будет достаточно большим. В любом случае, класс Config при инициализации подхватит эти свойства. Теперь надо как-то сделать так, чтобы команды Selenium-а могли работать и с картами элементов. По сути, для каждой команды должна быть конструкция вида:</p><p>
public void <selenium_command>( String arg0 ){
try {
if( Config.getProperty( arg0 ) != null ){
arg0 = Config.getProperty( arg0 );
}
}
catch( Exception e ){;}
selenium.<selenium_command>( arg0 );
}
</p><p>И подобные конструкции должны быть для всех методов. Для этого нужно расширить класс <strong>DefaultSelenium</strong>-а. Причем не просто расширить, а перегрузить практически все методы, у которых количество аргументов больше 0.</p><p> Сразу хочу предупредить, что подобный класс будет достаточно большим по объему кода (порядка 800-900 строк), причем весьма однообразного. Поэтому, для минимизации копирования/вставки кода удобно сделать кодогенератор. В Java есть такая вещь, как рефлексия, которая позволяет получить доступ к информации о классе, его методах, полях. Для таких языков как Ruby, Python есть даже более гибкие средства. В Java нам вначале достаточно сделать что-то наподобие:</p><p>
Class<?> clazz = Selenium.class;
Method[] methods = clazz.getDeclaredMethods();
</p><p>А потом уже в цикле пройтись по всем методам и сгенерировать текст нашего класса, выведя его в консоль. То есть сам кодогенератор выглядит так:</p><p>
package com.mycompany.selenium.utils;
import com.thoughtworks.selenium.Selenium;
import java.lang.reflect.Method;
/**
-
@author KaNoN
/
public class ExtGenerator {
/*-
@param args
/
public static void main(String[] args) {
Class<?> clazz = Selenium.class;
Method[] methods = clazz.getDeclaredMethods();
String classText = “package com.mycompany.selenium.lib;\r\n” +
"\r\nimport com.thoughtworks.selenium.;" +
“\r\n\r\n\r\npublic class ExtendedSelenium{\r\n” +
“\r\n\tprotected Selenium selenium = null;\r\n\r\n” +
“\tpublic ExtendedSelenium(String serverHost, int serverPort,\r\n” +
“\t\tString browserStartCommand, String browserURL) {\r\n” +
“\t\t\tselenium = new DefaultSelenium(serverHost, serverPort, browserStartCommand, browserURL);\r\n” +
“\t}\r\n\r\n”;for( Method method:methods ){
classText = classText + “\tpublic " + method.getReturnType().getName() + " " + method.getName() + “(”;
Class<?> classes[] = method.getParameterTypes();
for( int i = 0 ; i < classes.length ; i++ ){
classText = classText + " " + classes[i].getName() + " arg” + i;
if( i < classes.length - 1 ){
classText = classText + " , “;
}
}
classText = classText + " ){\r\n”;
if( classes.length == 0 ){
classText = classText + “\t\t” +
((method.getReturnType().toString().equals( “void” ) )?( “” ) "return " )) +
“selenium.” + method.getName() + “();\r\n\t}\r\n\r\n”;
}
else {
classText = classText + “\t\ttry {\r\n\t\t\tif( Config.getProperty( arg0 ) != null )” +
“{\r\n\t\t\t\targ0 = Config.getProperty( arg0 );\r\n\t\t\t}\r\n\t\t}\r\n” +
“\t\tcatch( Exception e ){;}\r\n”;
classText = classText + “\t\t” +
((method.getReturnType().toString().equals( “void” ) )?( “” ) “return " )) +
“selenium.” + method.getName() + “(”;
for( int i = 0 ; i < classes.length ; i++ ){
classText = classText + " arg” + i;
if( i < classes.length - 1 ){
classText = classText + " , “;
}
}
classText = classText + " );\r\n\t}\r\n\r\n”;
}
}classText = classText.replaceAll( “\[Ljava.lang.String;” , “String[]” );
classText = classText.replaceAll( “java.lang.” , “” );
classText = classText + “}”;
System.out.println( classText );
}
}
-
</p><p>Запустив это приложение мы потом можем создать класс com.mycompany.selenium.lib.ExtendedSelenium. Единственное, что в этот класс еще можно добавить, это некоторые расширенные методы. Например, методы, которые при клике или выборе элемента из списка еще ждали бы загрузку страницы. Дополним этот класс следующим кодом:</p><p>
public void clickAndWait( String locator ) throws Exception {
click( locator );
waitForPageToLoad( Config.getProperty( “delay” ) );
}
public void selectAndWait( String locator , String option ) throws Exception {
select( locator , option );
waitForPageToLoad( Config.getProperty( “delay” ) );
}
</p><p>После этого мы обновим <strong>com.mycompany.selenium.lib.BaseTestClass</strong>, заменив все вхождения <strong>DefaultSelenium</strong> на <strong>ExtendedSelenium</strong>. В результате этих изменений, мы уже можем использовать карту элементов вместо явно заданных локаторов, а совместные вызовы click и <strong>waitForPageToLoad </strong>можно заменить на <strong>clickAndWait</strong>. После таких преобразований наш тест приобретает вид:</p><p>
package com.mycompany.selenium.tests;
import com.mycompany.selenium.lib.BaseTestClass;
import java.util.Date;
import org.testng.annotations.Test;
/**
-
@author KaNoN
*/
public class SampleTestStage03 extends BaseTestClass {
private String genTaskName() {
Date dt = new Date();
String result = dt.toString();
result = result.replaceAll( “:” , “” );
result = result.replaceAll( " " , “” );
return result;
}
@Test(groups = {“sample”,“sample3”})
public void testCreateJob() throws Exception {
selenium.clickAndWait( “leftpanel.newjob” );
selenium.type(“newjobpage.name”, “SampleTask” + genTaskName() );
selenium.click(“newjobpage.freestyleradio”);
selenium.clickAndWait(“newjobpage.ok”);
selenium.clickAndWait(“configurejob.save”);
selenium.clickAndWait(“leftpanel.hudson”);
}
}
</p><p>Уже намного проще читать и понимать, что же выполняется, да и объем кода теста заметно уменьшился. То есть улучшения хорошо заметны. Но на этом останавливаться рано.</p><p>(Продолжение следует…)</p><p> </p><h2 style="text-align: right; "><a href="http://automated-testing.info/t/selenium-rc-java-2/2258" target="_blank" title="Selenium RC (Java): Шаги усовершенствования тестов. Часть 2">Часть 2</a></h2><p> </p>