Про Тестинг: обеспечение качества, тестирование, автоматизация

Раздел: Автоматизация > Практикум > Автоматизированное тестирование: работа со статическими ресурсами

Автоматизированное тестирование: работа со статическими ресурсами

Под статическим ресурсами понимаются те виды ресурсов, которые не изменяются в процессе тестирования или работы с приложением. К ним можно отнести названия и атрибуты элементов страниц, текст внутри элементов на странице, статусы документов и т.д.

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

Существует 2 основных варианта организации работы со статическими данными, которые имеют свои преимущества и недостатки:

  1. Прописывание данных внутри кода - hardcode (см. использование констант в Java) Недостатки:
    • при изменении данных необходимо будет по-новой пере-собирать тесты
    • все статические ресурсы загружаются и хранятся в памяти и не могут быть освобождены.
    Преимущества:
    • доступ к данным максимально упрощен
  2. Вынесение статических значений из кода (в БД, в файлы и т.д.) Недостатки:
    • операция чтения из БД или файлов занимает определенное время и может значительно замедлить выполнение тестов.
    • файл или таблица БД может "залочиться", в случае одновременного использования одних и тех же ресурсов
    Преимущества:
    • возможность структурирования статических ресурсов в БД или файловой системе
    • возможность изменения статических ресурсов без последующего изменения в коде и пере-собирания тестов
    • ресурсы из файла могу быть прочитаны, использованы тестом, удалены из памяти, а затем при необходимости снова прочитаны

По правде говоря, я не являюсь большим поклонником жесткого прописывания статических ресурсов в коде, хотя сторонников этого подхода хватает и у них есть веские причины следовать ему (неудобства и ограничения внутри IDE при работе с именами параметров, необходимость быстрого и максимально упрощенного доступа к данным).

Имея достаточное количество наработок при использовании файловой системы, в качестве контейнера для хранения статических ресурсов, я хотел бы поделиться своим методом работы с ними на базе Java Properties файлов.

Несколько слов о том, что такое проперти файл (Properties).

Это обычный текстовый файл, данные в котором хранятся в виде key = value
Пример: LoginPage.properties

field.username.id = UserName

field.password.id = Password

button.login.id = LoginButton

Пример Java кода, который читает LoginPage.properties файл и получает необходимую информацию:

// инициализация LoginPage.properties

Properties properties = new Properties();

File propertyFile = new File(LoginPage.properties);

properties.load(new FileReader(propertyFile));

// чтение данных

String userNameID = properties.getProperty("field.username.id");

String passwordID = properties.getProperty("field.password.id");

String loginButtonID = properties.getProperty("button.login.id");

Конечно избавиться от хардкода нам не удастся на все 100%, однако в последнем случае жестко прописаны ключи к для доступа к данным, а не сами данные. Что является уже не хардкодом, а скажем "параметризацией".

Теперь на базе этого я расскажу, как построен тестовый фреймворк основанный на примере описанном в предыдущей статье PageObjects pattern + Selenium (Java)

Основными принципами работы со статическими ресурсами в нашем приложении является то, то для каждого объекта страницы создается отдельный проперти файл с идентичным именем, хранящимися в проектном каталоге resources. Таким образом нам необходимо создать 3 файла и заполнить в них статическую информацию:

LoginPage.properties
field.username.locator = id

field.username.arg = UserName

field.password.locator = id

field.password.arg = Password

button.login.locator = id

button.login.arg = LoginButton
HomePage.properties
text.username.locator = id

text.username.arg = userName

link.logout.locator = id

link.logout.arg = LogoutLink
ErrorLoginPage.properties
text.errormessage.locator = id

text.errormessage.arg = ErrorMessage

link.backtologin.locator = id

link.backtologin.arg = BackLink

Создав все необходимые ресурсы, перейдем к написанию Java кода, который будет осуществлять загрузку, хранение и чтение данных. Для хранения загруженных ресурсов создадим класс DataStorage. В нем будет храниться Map данных (Map<String, Properties> propertiesMap), ключом в котором будет являться название объекта страницы, а значением объект Properties со списком статических данных.

DataStorage.java
public class DataStorage {

   private Map<String, Properties> propertiesMap;



   private DataStorage() {

       this.propertiesMap = new HashMap<String, Properties>();

   }



   public static DataStorage getInstance() {

       return new DataStorage();

   }



   public void setProperty(String key, Properties properties) {

       propertiesMap.put(key, properties);

   }



   public Properties getProperty(String key) {

       return propertiesMap.get(key);

   }



   public boolean exists(String key) {

       return propertiesMap.get(key) != null;

   }

}

Добавим ссылку на него в рабочем контексте, а также несколько сервисных методов для работы с ним (выделено жирным в листинге класса):

Context.java
public class Context {

   public static final String BROWSER_IE = "*iexplore";

   public static final String BROWSER_FF = "*firefox";

   public static final String BROWSER_CH = "*chrome";

   private static final String RESOURCES_PATH = "resources/${NAME}.properties";



   public static String siteUrl;

   private static Context context;

   private DataStorage dataStorage;

   private Selenium selenium;

   private SeleniumServer seleniumServer;



   private Context() {

       this.setDataStorage(DataStorage.getInstance());

   }



   public static void initInstance(String browserType, String siteURL) {

       context = new Context();

       siteUrl = siteURL;

       context.setSelenium(new DefaultSelenium("localhost", 4444, browserType, siteURL));

       context.start();

   }



   public static Context getInstance() {

       if (context == null) {

           throw new IllegalStateException("Context is not initialized");

       }

       return context;

   }



   public Selenium getSelenium() {

       if (selenium != null) {

           return selenium;

       }

       throw new IllegalStateException("WebBrowser is not initialized");

   }



   public void start() {

       try {

           seleniumServer = new SeleniumServer();

           seleniumServer.start();

       } catch (Exception e) {

           e.printStackTrace();

       }

       selenium.start();

   }



   public void close() {

       selenium.close();

       selenium.stop();

       seleniumServer.stop();

   }



   public String getSiteUrl() {

       return siteUrl;

   }



   public void setSelenium(Selenium selenium) {

       this.selenium = selenium;

   }



   public String getResourcesPath(String name) {

        return RESOURCES_PATH.replaceAll("\\$\\{NAME\\}", name);

    }



    private void setDataStorage(DataStorage dataStorage) {

        this.dataStorage = dataStorage;

    }



    public DataStorage getDataStorage() {

        return dataStorage;

    }

}

Теперь добавим в класс Page инициализацию ресурсов и сервисные методы для загрузки и чтения данных из файлов. Наиболее интересным из них будет являться метода initProperties(), который анализируя иерархию классов объекта страницы загружает необходимый проперти файл.

Page.java
public abstract class Page {

   private Context context;

   private String currentPage;

   private Properties properties;



   protected Page(String pageUrl) {

       this.currentPage = pageUrl;

       setContext(Context.getInstance());

       initProperties();

       init();

       parsePage();

   }



   private void initProperties() {

        String className = getClass().getSimpleName();

        if (!getContext().getDataStorage().exists(className)) {

            this.properties = new Properties();

            List superClasses = ClassUtils.getAllSuperclasses(getClass());

            File file = null;

            for (int i = superClasses.size() - 2; i >= 0 ; i--) {

                Class aClass = (Class) superClasses.get(i);

                file = new File(getResourcesPath(aClass.getSimpleName()));

                if (getContext().getDataStorage().getProperty(aClass.getSimpleName())== null) {

                    if (file.exists()) {

                        putAllProperties(file);

                        updateStorage(aClass.getSimpleName(), getProperties());

                    }

                } else {

                    putAllProperties(getContext().getDataStorage().getProperty(aClass.getSimpleName()));

                }

            }

            file = new File(getResourcesPath(className));

            putAllProperties(file);

            updateStorage(this, getProperties());

        } else {

            setProperties(getContext().getDataStorage().getProperty(getClass().getSimpleName()));

        }

    }



   protected abstract void init();

   protected abstract void parsePage();



   private void setContext(Context instance) {

       this.context = instance;

   }



   public Context getContext() {

       return context;

   }



   public String getCurrentPage() {

       return context.getSiteUrl() + this.currentPage;

   }



   protected Selenium getSelenium() {

       return context.getSelenium();

   }



   private String getResourcesPath(String name) {

       return getContext().getResourcesPath(name);

   }



   private Properties getProperties() {

       return properties;

   }



   private void setProperties(Properties properties) {

       this.properties = properties;

   }



   protected String getProperty(String key) {

       return properties.getProperty(key);

   }



   private void putAllProperties(File proertiesFile) {

       try {

           this.properties.load(new FileReader(proertiesFile));

       } catch (IOException e) {

           e.printStackTrace();

       }

   }



   private void putAllProperties(Properties properties) {

       this.properties.putAll(properties);

   }



   private void updateStorage(Object parentKeyObj, Properties properties) {

       updateStorage(parentKeyObj.getClass().getSimpleName(), properties);

   }



   private void updateStorage(String className, Properties properties) {

       getContext().getDataStorage().setProperty(className, (Properties)properties.clone());

   }



   protected String buildLocator(String type, String arg) {

       // Сервисный метода для создания Selenium локатора

       // по двум параметрам тип и аргумент

       return type + "=" + arg;

   }



   // ....

   // service methods...

   // ....

}

Заменив в объектах станиц, конкретные значения статических ресурсов на чтение их значений из объектов Properties мы получаем их следующую реализацию:

LoginPage.java
public class LoginPage extends Page {

   public static final String PAGE_URL = "http://www.testlogin.com/login.html";



    protected LoginPage() {

        super(PAGE_URL);

    }



    public static LoginPage openLoginPage() {

        LoginPage loginPage = new LoginPage();

        loginPage.getSelenium().open(PAGE_URL);

        return loginPage;

    }



    private void setUserName(String userName) {

        // код для заполнения поля Username

        getSelenium().type(buildLocator(getProperty("field.username.locator"),getProperty("field.username.arg")), userName);

           }



    private void setPassword(String password) {

        // код для заполнения поля Password

        getSelenium().type(buildLocator(getProperty("field.password.locator"), getProperty("field.password.arg")), password);

   }



    private void pushLoginButton() {

        // код для нажатия на кнопку Login

        getSelenium().click(buildLocator(getProperty("button.login.locator"), getProperty("button.login.arg")));

   }



    protected void parsePage() {

        // Разбор элементов страницы

        // Заполнение необходимых переменных данными со страницы

    }



    protected void init() {

        // Инициализация страницы

        // Проверка корректности загрузки

        if(!getSelenium().getLocation().equals(PAGE_URL)) {

            throw new IllegalStateException("Invalid page is opened");

        }

    }



    private void loginAs(String userName, String password) {

        setUserName(userName);

        setPassword(password);

        pushLoginButton();

    }



    public HomePage login(String userName, String password) {

        loginAs(userName, password);

        return new HomePage();

    }



    public ErrorLoginPage loginInvalid(String userName, String password) {

        loginAs(userName, password);

        return new ErrorLoginPage();

    }

}

HomePage.java
public class HomePage extends Page {

    public static final String PAGE_URL = "http://www.testlogin.com/home.html";

    private String loggedinUserName;



    protected HomePage() {

        super(PAGE_URL);

    }



    protected void init() {

// Инициализация страницы

    }



    protected void parsePage() {

// Разбор элементов страницы

        this.loggedinUserName = getSelenium().getText(buildLocator(getProperty("text.username.locator"), getProperty("text.username.id")));

   }



    public String getLoggedinUserName() {

        return loggedinUserName;

    }



    public LoginPage logout() {

        getSelenium().click(buildLocator(getProperty("link.logout.locator"), getProperty("link.logout.id")));

       return new LoginPage();

    }

}

ErrorLoginPage.java
public class ErrorLoginPage extends Page {

    public static final String PAGE_URL = "http://www.testlogin.com/loginError.html";

    private String errorMessage;



    protected ErrorLoginPage() {

        super(PAGE_URL);

    }



    protected void init() {

        // Инициализация страницы

    }



    protected void parsePage() {

        this.errorMessage = getSelenium().getText(buildLocator(getProperty("text.errormessage.locator"), getProperty("text.errormessage.id")));

   }



    public String getErrorMessage() {

        return this.errorMessage;

    }



    public LoginPage backToLoginPage() {

        getSelenium().click(buildLocator(getProperty("link.backtologin.locator"), getProperty("link.backtologin.id")));

       return new LoginPage();

    }

}

Скачать исходный код примеров по работе со статическими ресурсами (Selenium 1.x.x)

Обратите внимание, что в теперешней реализации мы вынесли все статические ресурсы в файлы. И теперь в случае, если какие-то данные будут изменены, например, id “UserName” изменится на “user_name” и нужно будет изменить тип локатора для поиска c “id” на “xpath”, то нам всего навсего надо будет заменить значение в файле, оставив код фреймворка без изменения:

field.username.locator = xpath

field.username.arg = //input[@id=’user_name’]

Описанный выше подход при работе со статическими ресурсами, используется уже на протяжении двух лет, и значит он в полевых условиях доказал свое право на существование. Надеюсь, что после внимательного прочтения кому-то тоже захочется реализовать у себя что-то похожее, либо абсолютно такое же.


Автор: Алексей Булат

Наверх