Java

Тестируем REST API с Retrofit2

У меня полностью разработана система на Java, которая тестирует запросы в REST API, получая заранее подготовленные данные из json файлов. Здесь я покажу минимальные куски кода, которые помогут понять, как создать Java классы, которые будут содержать все ендпоинты вашего REST API. Для начала нужно установить Retrofit 2 и кажется он требует okhttp3:

<dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>retrofit</artifactId>
            <version>2.1.0</version>
</dependency>
<dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.4.1</version>
</dependency>

Создадим файл ApiService.java, которые будет содержать запросы в API:

public interface ApiService {
    @GET("api/user")
    Call<JsonElement> getUser(@Header("Accept-Language") String locale,
                              @Header("Authorization") String token);
}

GET это тип запроса, в скобках путь к ендпоинту апи. Внутри - заголовки которые мы передаем.

Ещё мы создадим класс, который тоже будет содержать методы, каждый из которых будет вызывать методы из класса ApiService, но с индивидуальными обработчиками. Зачем это нужно? Ну, например, каждый из запросов порождает определенного формата ответ от сервера, и модели этих ответов мы можем создать заранее, и легко работать потом с данными из этих ответов прямо в тестах. Вот пример метода из класса с запросами ApiEndpoints.java:

public class ApiEndpoints {
  public static UserToken getUser(String token) throws IOException {
        String locale = ConfigurationInstance.getInstance().getLocale(); // получение значения из конфигурации
        String authHeaderValue = "Bearer " + token;
        Call<JsonElement> call = RestClient.getInstance().getApiService().getUser(locale, authHeaderValue);
        Response response = call.execute();
        Object responseObject = response.body();
        Gson gson = new Gson();
        String json = responseObject.toString();
        UserToken responseUserToken = gson.fromJson(json, UserToken.class);
        return responseUserToken;
    }
}

Тут ответ преобразуется в тип UserToken. Это модель ответа сервера на такой запрос и она создана заранее.

Ну и наконец вызов запроса из самого теста:

@Test(description = "GET /api/user", groups = {"User"})
    public void getUser(ITestContext context) throws IOException, InterruptedException {
        String token = (String) context.getAttribute("token"); //токен берется из конфигураций или прошлых запросов
        UserToken userTokenResponse = ApiEndpoints.getUser(token); //вызов запроса
    }

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

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

Сурсы в этом посте немного пообрезаны и могут не работать в чистом виде, но думаю разобраться что нужно добавить будет уже не сложно.

Нахрен CSV, давай парсить тест данные из json

CSV слишком простой и тупой для хранения данных тестов, предлагаю хранить эти данные в json, т.к. json огонь и его все любят.

1 Ставим json.simple через Maven:

<dependency>  
    <groupId>com.googlecode.json-simple</groupId>  
    <artifactId>json-simple</artifactId>  
    <version>1.1.1</version>  
</dependency>

2 Пишем метод, который принимает как сырые данные json формата, так и путь к .json файлу:

public static JSONObject parseJsonData(String data) throws ParseException, IOException {  
    boolean isJsonContent = data.substring(0, 1).matches("\\{");  
    Object object;  
    JSONParser parser = new JSONParser();  
    if (isJsonContent) object = parser.parse(data);  
    else object = parser.parse(new FileReader(data));  
    return (JSONObject) object;  
}

3 Он возвращает обьект типа JSONObject, с которым можно ковыряться удобными способами:

Object jsonRequests = jsonObject.get("Requests");  
  
int currentRequestId = 0;  
  
JSONObject request = (JSONObject) jsonRequests.get(currentRequestId);  
String requestName = (String) request.get("Name");  
JSONArray testCases = (JSONArray) request.get("TestCases");

Из JSONObject можно выковыривать любые данные, а с JSONArray работать как с обычным массивом.

Как передать параметр из Jenkins -> ANT -> TestNG

  1. В Jenkins идем в настройки Джоба и выставляем "This build is parameterized"
  2. Называем параметр "additionalScreens" например
  3. В ant.xml проставляем
    <property name="additionalScreens" value=""/>
    
  4. В ant.xml в таргете не забываем передать этот параметр в TestNG:
    <target name="Test.Real" depends="compile">  
            <testng outputdir="${testdir}" classpathref="all.classpath" haltOnFailure="true" testnames="Buy">  
                <xmlfileset dir="${basedir}" includes="${testngxml}"/>  
                <jvmarg value="-DadditionalScreens=${additionalScreens}" />  
            </testng>  
    </target>
    
  5. В TestNG.xml параметр никак не фигурирует, там просто вызывается тест:
    <test verbose="1" name="Buy" annotations="JDK">  
        <classes>  
            <class name="TestNG.Buy">  
                <methods>  
                    <include name="shop"/>  
                    <include name="Scan"/>  
                    <include name="Buy"/>  
                </methods>  
            </class>  
        </classes>  
    </test>
    
  6. В методе @BeforeClass принимаем параметр и дальше работаем с ним как хотим:
    @Parameters({ "additionalScreens" })  
        @BeforeClass  
        public void prepare(String additionalScreens, ITestContext context) throws Exception {  
            context.setAttribute("additionalScreens", additionalScreens);  
            //......  
    }
    
  7. Запускаем Джоб в Дженкинсе и заполняем или выбираем параметр "additionalScreens" - он передается в тест. Это может быть, например, урл сервера, или тип проверки, и т.д.
    Не благодари!

Как в TestNG передавать переменные между файлами классов

Легко. Если у вас тестСьюты лежат не только в разных классах, но также и в разных .java файлах, нужные переменные можно передавать, записывая их в ITestContext в одном классе, а потом читая их оттудаже в другом классе. ITestContext это данные, которые могут перезаписывать или использовать любые тест методы в TestNG.

Записываем данные:

@Test(description="Найти код")  
public void check(ITestContext context) {  
    data = SomeClass.doSomething(driver, data, config);  
    context.setAttribute("myVariable", data);  
}

Читаем их в другом классе:

@Test(description="test2")  
public void test2(ITestContext context) {  
    String data = (String) context.getAttribute("myVariable");  
}

Как с помошью Selenium зааплодить файл в невидимый инпут

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

public static void jsDeleteClasses(String xpath, WebDriver driver) throws InterruptedException {  
    JavascriptExecutor js = (JavascriptExecutor) driver;  
    String script = "document.evaluate(\""+xpath+"\", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)" +  
            ".singleNodeValue.setAttribute('class', '');";  
    js.executeScript(script);  
}

Можно переделать чтоб удаляла атрибут style, например.