вторник, 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();

Комментариев нет:

Отправить комментарий