Показаны сообщения с ярлыком serialization. Показать все сообщения
Показаны сообщения с ярлыком serialization. Показать все сообщения

пятница, 20 апреля 2012 г.

Отделяем данные от тестов с помощью сериализации

Если вы хотите написать тест не зависящий от данных, или вам при тестировании приходится часто менять тестовые данные - эта статья для вас.

1. В чем заключается проблема ?
Тестовые данные могут изменяться и жестко заносить их в код тестов не самая лучшая идея. Гораздо лучшей идеей было -бы хранить эти данные отдельно и предоставить к ним доступ всем необходимым тестам.

2. Как этого добиться ?
Я усматриваю несколько путей к реализации такого доступа.
1) Хранить данные в БД
2) Properties
3) Сериализация

Я не буду рассматривать все плюсы и минуса каждого подхода, а конкретно поговорю о последнем.

Сериализация - процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности.

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

Есть решение этого. Сериализация в XML файл. В итоге такой сериализации мы получим на выходе читабельный и редактируемый файл.

 Рассмотрим пример. Напишем класс который хранит в себе логин и пароль пользователя.

public class LoginInfo{
  String login = "name";
  String password = "pass";
}

В классе всего 2 строковых поля. Как данный класс будет выглядеть в сериализованном виде ?
Давайте посмотрим.
Для сериализации я использую библиотеку XStream.

LoginInfo info= new LoginInfo();
System.out.println(new XStream().toXml(info));

Выполнение этих двух строчек кода выведет нам в консоль примерно следующее

<LoginInfo>
 <login>name</login>
 <password>pass</password>
</LoginInfo>

сохранив эту информация в xml файл мы можем восстановить объект оттуда.

LoginInfo info2= (LoginInfo) new XStream().fromXml(new FileInputStream("file.xml"));

Таким образом мы можем однажды описать структура с данными и хранить ее во внешнем файле. и при необходимости менять эти данные и при десериализации они будут подтягиваться в ваш код, необходимость менять сам код отпадает.

пример теста использующего этот подход:

LoginInfo info=null;

@BeforeMethod
public void before(){
 info=(LoginInfo) new XStream().fromXml(new FileInputStream("file.xml"));
}

@Test
public void test(){
 FirefosDriver firefox = new FirefoxDriver();
firefox.findElement(By...).sendKeys(info.login);
firefox.findElement(By...).sendKeys(info.password);
}



 

вторник, 3 апреля 2012 г.

Использование рефлексии при написании тестов на Selenium

Сегодня я расскажу об использовании рефлексии языка при написании тестов.

Что это и для чего нужно ?

Рефлексия - процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения.

Это бывает необходимо если вы хотите создать достаточно гибкую систему тестов, которая будет легко подвергаться настройке и изменения без внесения изменений в код.

Пример 

Предположим вы написали некий класс, описывающий веб-страницу с использованием Selenium PageFactory. 
В ходе выполнения тестов выяснилось что местоположение и идентификаторы элементов за аннотированных как @FindBy могут часто меняться. 
Как тогда быть ? 
Постоянно переписывать все аннотации ? 
Или вынести все аннотации по внешний файл с настройками и менять их там без перекомпиляции кода ?

Последний вариант на мой взгляд наиболее приемлем.

 Реализация

Для реализации нашего варианта нам и потребуется рефлексия. 

Будем использовать библиотеку Javassist.
Ее использование дает нам самые широкие возможности по изменению кода в рантайме. Вплоть до изменения аннотаций !!!

пусть у нас в некоем классе описан элемент:

@FindBy(how = How.ID, using = "element_id")
    private WebElement element;

мы хотим в рантайме менять аттрибуты how и using у аннотации.
значения using будем подгружать из файла настроек.

Для этого напишем класс-адаптер:

public class InterfaceAdaptor {

public static Class adapt(String classname) {
        Properties props=new Properties();
        props.load(new FileInputStream(new File(classname+".props"))); //загружаем файл с настройками
        ClassPool pool = ClassPool.getDefault(); // получаем пулл классов

        CtClass cc = pool.getCtClass(classname); // получаем наш класс из пула
        CtField[] fields=cc.getDeclaredFields(); // получаем список всех полей
        ClassFile ccFile = cc.getClassFile(); // получаем сам класс-файл
        ConstPool constpool = ccFile.getConstPool();
        for(CtField f:fields){
            if(props.containsKey(f.getName())){ 
                // если в пропертис находим значение соответствующее имени поля
                // устанавливаем для этого поля аннотацию с нужными нам параметрами
                //новая аннотация перезаписывает старую
                 AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
                 Annotation annot = new Annotation("org.openqa.selenium.support.FindBy", constpool);
                 EnumMemberValue e=new EnumMemberValue(ccFile.getConstPool());
                 e.setType("org.openqa.selenium.support.How");
                 e.setValue("XPATH");
                 annot.addMemberValue("how",e );
                 annot.addMemberValue("using", new      StringMemberValue(props.getProperty(f.getName()),ccFile.getConstPool()));
               
                 attr.addAnnotation(annot); //добавляем аннотация
                 f.getFieldInfo().addAttribute(attr); //добавляем аттрибуты
                //
            }
        }
        return cc.toClass(); // возвращаем новый исправленный класс
    }
}

После того как мы получили класс мы можем опять таки через рефлексию получить его инстанс.
InterfaceAdaptor.adapt("org.your.ClassName").getConstructors()[0].newInstance();