Slide 1

Slide 1 text

API Виктор Орловский Инженер по автоматизации тестирования Автотесты Автотесты на на

Slide 2

Slide 2 text

Зачем это нужно? Первый автотест Инструменты Подходы Советы и полезные практики План лекции

Slide 3

Slide 3 text

Web интерфейс

Slide 4

Slide 4 text

Почта для мобильного браузера Web интерфейс

Slide 5

Slide 5 text

Почта для мобильного браузера Web интерфейс Мобильное приложение

Slide 6

Slide 6 text

Почта для мобильного браузера Web интерфейс Мобильное приложение API

Slide 7

Slide 7 text

API Application Programming Interface Что такое API? (Web API)

Slide 8

Slide 8 text

API API определяет функциональность *при этом API не раскрывает, как именно эта функциональность реализована Програмный интерфейс приложения

Slide 9

Slide 9 text

APP APP

Slide 10

Slide 10 text

APP APP API Приложения взаимодействуют друг с другом посредством API

Slide 11

Slide 11 text

APP APP API HTTP response HTTP request API в качестве транспорта использует протокол HTTP

Slide 12

Slide 12 text

APP APP API HTTP response HTTP request JSON/XML Набор определенных HTTP запросов, а также определение структуры HTTP-ответов

Slide 13

Slide 13 text

HTTP (Hypertext Transfer protocol) Протокол HTTP

Slide 14

Slide 14 text

Задача ● Проверить статус код и возвращаемое значение curl -v http://weather.lanwen.ru/api/weather?city=Moscow > GET /api/weather?city=Moscow HTTP/1.1 > User-Agent: curl/7.19.7 > Host: weather.lanwen.ru > Accept: */* > < HTTP/1.1 200 OK < Date: Tue, 19 Apr 2016 16:27:33 GMT < Content-Type: application/json < Content-Length: 293 < Server: Jetty(9.2.1.v20140609) < {"city":"Moscow","daypart":"DAY","weathercode":800, "sunset":1461084341,"sunrise":1461031950,"dt":1461080976, "humidity":57,"wind":5.0, "temperatures":[{"unit":"°C","value":9.0}, {"unit":"°K","value":292.89}, {"unit":"°F","value":49.53200000000002}, {"unit":"°Kaif","value":20.0}]}

Slide 15

Slide 15 text

Структура протокола Стартовая строка (Request Line) ● Метод URI HTTP/Версия ● HTTP/Версия код пояснение

Slide 16

Slide 16 text

Задача ● Проверить статус код и возвращаемое значение curl -v http://weather.lanwen.ru/api/weather?city=Moscow > GET /api/weather?city=Moscow HTTP/1.1 > User-Agent: curl/7.19.7 > Host: weather.lanwen.ru > Accept: */* > < HTTP/1.1 200 OK < Date: Tue, 19 Apr 2016 16:27:33 GMT < Content-Type: application/json < Content-Length: 293 < Server: Jetty(9.2.1.v20140609) < {"city":"Moscow","daypart":"DAY","weathercode":800, "sunset":1461084341,"sunrise":1461031950,"dt":1461080976, "humidity":57,"wind":5.0, "temperatures":[{"unit":"°C","value":9.0}, {"unit":"°K","value":292.89}, {"unit":"°F","value":49.53200000000002}, {"unit":"°Kaif","value":20.0}]}

Slide 17

Slide 17 text

Структура протокола Стартовая строка (Request Line) Заголовки (Message Headers) ● Основные заголовки ● Заголовки запроса ● Заголовки ответа ● Заголовки сущности ● Метод URI HTTP/Версия ● HTTP/Версия код пояснение

Slide 18

Slide 18 text

Задача ● Проверить статус код и возвращаемое значение curl -v http://weather.lanwen.ru/api/weather?city=Moscow > GET /api/weather?city=Moscow HTTP/1.1 > User-Agent: curl/7.19.7 > Host: weather.lanwen.ru > Accept: */* > < HTTP/1.1 200 OK < Date: Tue, 19 Apr 2016 16:27:33 GMT < Content-Type: application/json < Content-Length: 293 < Server: Jetty(9.2.1.v20140609) < {"city":"Moscow","daypart":"DAY","weathercode":800, "sunset":1461084341,"sunrise":1461031950,"dt":1461080976, "humidity":57,"wind":5.0, "temperatures":[{"unit":"°C","value":9.0}, {"unit":"°K","value":292.89}, {"unit":"°F","value":49.53200000000002}, {"unit":"°Kaif","value":20.0}]}

Slide 19

Slide 19 text

Структура протокола Стартовая строка (Request Line) Заголовки (Message Headers) Тело сообщения или тело объекта (Entity Body) ● Основные заголовки ● Заголовки запроса ● Заголовки ответа ● Заголовки сущности message-body = entity-body | ● Метод URI HTTP/Версия ● HTTP/Версия код пояснение

Slide 20

Slide 20 text

Задача ● Проверить статус код и возвращаемое значение curl -v http://weather.lanwen.ru/api/weather?city=Moscow > GET /api/weather?city=Moscow HTTP/1.1 > User-Agent: curl/7.19.7 > Host: weather.lanwen.ru > Accept: */* > < HTTP/1.1 200 OK < Date: Tue, 19 Apr 2016 16:27:33 GMT < Content-Type: application/json < Content-Length: 293 < Server: Jetty(9.2.1.v20140609) < {"city":"Moscow","daypart":"DAY","weathercode":800, "sunset":1461084341,"sunrise":1461031950,"dt":1461080976, "humidity":57,"wind":5.0, "temperatures":[{"unit":"°C","value":9.0}, {"unit":"°K","value":292.89}, {"unit":"°F","value":49.53200000000002}, {"unit":"°Kaif","value":20.0}]}

Slide 21

Slide 21 text

Протокол HTTPS

Slide 22

Slide 22 text

HTTP (Hypertext Transfer protocol) + SSL (Secure Socket Layer) HTTPS (Hypertext Transfer Protocol Secure) =

Slide 23

Slide 23 text

Зачем автоматизировать тестирование API?

Slide 24

Slide 24 text

Почта для мобильного браузера Web интерфейс Мобильное приложение API

Slide 25

Slide 25 text

Почта для мобильного браузера Web интерфейс Мобильное приложение Тут! И тут тоже! API И тут!

Slide 26

Slide 26 text

API реже меняется по сравнению с версткой

Slide 27

Slide 27 text

Верстка почты в 2015 году API внутри

Slide 28

Slide 28 text

Верстка почты в 2016 году Тот же API внутри

Slide 29

Slide 29 text

● Работа с непреобразованными данными JSON String int ?

Slide 30

Slide 30 text

10 секунд 5 милисекунд curl -k -b "Session_id=...; Sessionid2=..." "https://mail.yandex.ru/api/mailbox_list" 500 милисекунд

Slide 31

Slide 31 text

Плюсы автотестов на API: + Легко поддерживать + Работают с непреобразованными данными + Быстрые и стабильные Автотесты на API нужны для того, чтобы проверять функциональность!

Slide 32

Slide 32 text

Зачем это нужно? Первый автотест Инструменты Подходы Советы и полезные практики План лекции

Slide 33

Slide 33 text

Задача ● Проверить статус код и возвращаемое значение curl -v http://weather.lanwen.ru/api/weather?city=Moscow > GET /api/weather?city=Moscow HTTP/1.1 > User-Agent: curl/7.19.7 > Host: weather.lanwen.ru > Accept: */* > < HTTP/1.1 200 OK < Date: Tue, 19 Apr 2016 16:27:33 GMT < Content-Type: application/json < Content-Length: 293 < Server: Jetty(9.2.1.v20140609) < {"city":"Moscow","daypart":"DAY","weathercode":800, "sunset":1461084341,"sunrise":1461031950,"dt":1461080976, "humidity":57,"wind":5.0, "temperatures":[{"unit":"°C","value":9.0}, {"unit":"°K","value":292.89}, {"unit":"°F","value":49.53200000000002}, {"unit":"°Kaif","value":20.0}]}

Slide 34

Slide 34 text

https://github.com/apache/httpclient ● Одна из первых библиотек для работы с HTTP ??

Slide 35

Slide 35 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 36

Slide 36 text

@Test public void weatherTest() throws IOException, JSONException { //создаем клиент по умолчанию HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 37

Slide 37 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); //формируем status line запроса String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 38

Slide 38 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); //выполняем запрос Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 39

Slide 39 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); //получаем ответ HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 40

Slide 40 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); //достаем статус код из ответа и проверяем его int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 41

Slide 41 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); //получаем тело сообщения String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 42

Slide 42 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); //приводим тело сообщения к JSON и проверяем поле city JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 43

Slide 43 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); /* получаем JSON массив температур и проверяем количество и первый элемент */ JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 44

Slide 44 text

Зачем это нужно? Первый автотест Инструменты Подходы Советы и полезные практики План лекции

Slide 45

Slide 45 text

Инструменты А можно проще быстрей?

Slide 46

Slide 46 text

https://github.com/apache/httpclient ● Одна из первых библиотек для работы с HTTP ??

Slide 47

Slide 47 text

https://github.com/apache/httpclient ● Одна из первых библиотек для работы с HTTP

Slide 48

Slide 48 text

Минусы HTTP client* - Приходится писать очень много кода - Много поддержки * тем не менее используется очень часто

Slide 49

Slide 49 text

Инструменты высокого уровня

Slide 50

Slide 50 text

BUILD REQUEST ADD FILTERS EXEC REQUEST PARSE RESPONSE

Slide 51

Slide 51 text

https://github.com/jayway/rest-assured ● предназначен для тестирования REST сервисов ● синтаксис groovy/ruby + куча сахара ● внутри HTTP клиент ● json-path, xml-path REST-assured Jayway

Slide 52

Slide 52 text

@Test public void simpleTest() throws Exception { given().baseUri("http://weather.lanwen.ru") .basePath("api").param("city", "Moscow") .get("weather") .then().assertThat().statusCode(HttpStatus.SC_OK) .and().body("city", equalTo("Moscow")) .and().body("temperatures", hasSize(4)) .and().body("temperatures[0].value", equalTo(9.0f)); }

Slide 53

Slide 53 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 54

Slide 54 text

@Test public void simpleTest() throws Exception { //устанавливаем спецификацию по умолчанию, можно when() given().baseUri("http://weather.lanwen.ru") .basePath("api").param("city", "Moscow") .get("weather") .then().assertThat().statusCode(HttpStatus.SC_OK) .and().body("city", equalTo("Moscow")) .and().body("temperatures", hasSize(4)) .and().body("temperatures[0].value", equalTo(9.0f)); }

Slide 55

Slide 55 text

@Test public void simpleTest() throws Exception { //формируем status line запроса через базовый uri given().baseUri("http://weather.lanwen.ru") .basePath("api").param("city", "Moscow") //здесь выполненияем запрос и получаем ответ .get("weather") .then().assertThat().statusCode(HttpStatus.SC_OK) .and().body("city", equalTo("Moscow")) .and().body("temperatures", hasSize(4)) .and().body("temperatures[0].value", equalTo(9.0f)); }

Slide 56

Slide 56 text

@Test public void simpleTest() throws Exception { given().baseUri("http://weather.lanwen.ru") .basePath("api").param("city", "Moscow") .get("weather") //получаем ответ для валидации .then().assertThat().statusCode(HttpStatus.SC_OK) .and().body("city", equalTo("Moscow")) .and().body("temperatures", hasSize(4)) .and().body("temperatures[0].value", equalTo(9.0f)); }

Slide 57

Slide 57 text

@Test public void simpleTest() throws Exception { given().baseUri("http://weather.lanwen.ru") .basePath("api").param("city", "Moscow") .get("weather") //проверяем статус код. Здесь assertThat() - сахар .then().assertThat().statusCode(HttpStatus.SC_OK) .and().body("city", equalTo("Moscow")) .and().body("temperatures", hasSize(4)) .and().body("temperatures[0].value", equalTo(9.0f)); }

Slide 58

Slide 58 text

@Test public void simpleTest() throws Exception { given().baseUri("http://weather.lanwen.ru") .basePath("api").param("city", "Moscow") .get("weather") .then().assertThat().statusCode(HttpStatus.SC_OK) //используя JSON-path проверяем тело сообщения .and().body("city", equalTo("Moscow")) //and() - сахар .and().body("temperatures", hasSize(4)) .and().body("temperatures[0].value", equalTo(9.0f)); }

Slide 59

Slide 59 text

Минусы REST-assured - Довольно тяжел (тянет множество ненужных библиотек) Groovy HTTP client

Slide 60

Slide 60 text

Минусы REST-assured - Встроенные проверки не всегда читаемы

Slide 61

Slide 61 text

https://github.com/square/retrofit ● Превращает REST API сервис в Java Interface ● Внутри OkClient Square

Slide 62

Slide 62 text

public interface MyService { @GET("/api/weather") Call weather(@Query("city") String city); } public class WeatherResp { public String city; public String getCity() { return city; } public List temperatures; public List getTemperatures() { return temperatures; } } Интерфейс API Класс ответа

Slide 63

Slide 63 text

public class Temperature { public float value; public String getValue() { return value; } public String unit; public String getUnit() { return unit; } } Вспомогательный класс ответа

Slide 64

Slide 64 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.lanwen.ru") .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = retrofit.create(MyService.class); Call req = service.weather("Moscow"); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 65

Slide 65 text

@Test public void weatherTest() throws IOException, JSONException { HttpClient hc = HttpClientBuilder.create().build(); String uri = "http://weather.lanwen.ru/api/weather?city=Moscow"; Request request = Request.Get(uri); Response response = Executor.newInstance(hc).execute(request); HttpResponse httpResponse = response.returnResponse(); int statusCode = httpResponse.getStatusLine().getStatusCode(); assertThat(statusCode, equalTo(HttpStatus.SC_OK)); String entity = EntityUtils.toString(httpResponse.getEntity()); JSONObject resp = new JSONObject(entity); assertThat(resp.get("city"), equalTo("Moscow")); JSONArray temperatures = resp.getJSONArray("temperatures"); assertThat(temperatures.length(), equalTo(4)); assertThat(temperatures.getJSONObject(0).get("value"), equalTo(9.0)); }

Slide 66

Slide 66 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { //создаем объект ретрофита Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.lanwen.ru") .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = retrofit.create(MyService.class); Call req = service.weather("Moscow"); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 67

Slide 67 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { Retrofit retrofit = new Retrofit.Builder() //указываем базовый url .baseUrl("http://weather.lanwen.ru") .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = retrofit.create(MyService.class); Call req = service.weather("Moscow"); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 68

Slide 68 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.lanwen.ru") //добавляем конвертер (отдельная зависимость) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = retrofit.create(MyService.class); Call req = service.weather("Moscow"); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 69

Slide 69 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.lanwen.ru") .addConverterFactory(GsonConverterFactory.create()) .build(); //генерируем API клиент по интерфейсу MyService service = retrofit.create(MyService.class); Call req = service.weather("Moscow"); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 70

Slide 70 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.lanwen.ru") .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = retrofit.create(MyService.class); //получаем запрос с параметром Moscow Call req = service.weather("Moscow"); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 71

Slide 71 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.lanwen.ru") .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = retrofit.create(MyService.class); Call req = service.weather("Moscow"); //выполняем запрос, получаем объект ответа Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 72

Slide 72 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.lanwen.ru") .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = retrofit.create(MyService.class); Call req = service.weather("Moscow"); Response resp = req.execute(); //проверяем статус код assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 73

Slide 73 text

@Test public void pingShouldSeePong() throws IOException { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); Retrofit restAdapter = new Retrofit.Builder() .baseUrl("http://localhost") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = restAdapter.create(MyService.class); Call req = service.ping(); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); assertThat(resp.body().getPong(), equalTo("")); } @Test public void simpleTest() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.lanwen.ru") .addConverterFactory(GsonConverterFactory.create()) .build(); MyService service = retrofit.create(MyService.class); Call req = service.weather("Moscow"); Response resp = req.execute(); assertThat(resp.code(), is(HttpStatus.SC_OK)); //проверяем тело ответа assertThat(resp.body().getCity(), equalTo("Moscow")); assertThat(resp.body().getTemperatures(), hasSize(4)); assertThat(resp.body().getTemperatures().get(0).getValue(), equalTo(9.0)); }

Slide 74

Slide 74 text

Минусы Retrofit - Изначально не для тестов - Мало документации http://square.github.io/retrofit/

Slide 75

Slide 75 text

Инструмент нужно выбирать под задачу

Slide 76

Slide 76 text

Зачем это нужно? Первый автотест Инструменты Подходы Советы и полезные практики План лекции

Slide 77

Slide 77 text

Ожидание формируем сами Подходы к тестированию API

Slide 78

Slide 78 text

Результат теста Сравниваем с подготовленным результатом Ожидание формируем сами Ответ

Slide 79

Slide 79 text

assertThat(city, equalTo("Moscow")); assertThat(temperatures, hasSize(4)); assertThat(temperatures.get(0).getValue(), equalTo(9.0));

Slide 80

Slide 80 text

assertThat(city, equalTo("Moscow")); assertThat(temperatures, hasSize(4)); assertThat(temperatures.get(0).getValue(), equalTo(9.0)); Подготовленный результат

Slide 81

Slide 81 text

Отлично подходит для модифицирующих операций ● Небольшой ответ от API ● Понятно что произошло *появился/удалился/изменился какой-то объект { "result" : "ok", "error" : "" }

Slide 82

Slide 82 text

+ Можно гибко настраивать спецификацию - Поддержка соответствия спецификации

Slide 83

Slide 83 text

… и еще 200 строк Протестируй это! [{ "_id": 498817, "name": "Saint Petersburg", "country": "RU", "coord": { "lon": 30.264168, "lat": 59.894444 } }, { "_id": 524901, "name": "Moscow", "country": "RU", "coord": { "lon": 37.615555, "lat": 55.75222 } }, { "_id": 472757, "name": "Volgograd", "country": "RU", "coord": { "lon": 44.501839, "lat": 48.719391 } }, curl http://weather.lanwen.ru/api/cities

Slide 84

Slide 84 text

Ожидание предсказываем Подходы к тестированию API

Slide 85

Slide 85 text

Результат теста Сравниваем с автоматически предсказанным результатом Ответ Ожидание предсказываем

Slide 86

Slide 86 text

Различия Сравниваем Версия 2 Сравнение со стабильной версией Версия 1

Slide 87

Slide 87 text

assertThat(testResp, noDiffWith(prodResp));

Slide 88

Slide 88 text

assertThat(testResp, noDiffWith(prodResp)); Предсказанный результат

Slide 89

Slide 89 text

[{ "_id": 498817, 498818 "name": "Saint Petersburg", "country": "RU", "coord": { "lon": 30.264168, "lat": 59.894444 } }, { "_id": 524901, "name": "Moscow", "Москва" "country": "RU", "coord": { "lon": 37.615555, "lat": 55.75222 } }] Версия 1 Отлично подходит для НЕмодифицирующих операций Версия 2 Различия [{ "_id": 498817, "name": "Saint Petersburg", "country": "RU", "coord": { "lon": 30.264168, "lat": 59.894444 } }, { "_id": 524901, "name": "Moscow", "country": "RU", "coord": { "lon": 37.615555, "lat": 55.75222 } }] [{ "_id": 498818, "name": "Saint Petersburg", "country": "RU", "coord": { "lon": 30.264168, "lat": 59.894444 } }, { "_id": 524901, "name": "Москва", "country": "RU", "coord": { "lon": 37.615555, "lat": 55.75222 } }]

Slide 90

Slide 90 text

+ Не поддерживаем актуальную спецификацию - Поддержка состояния - Нет гибкости - Ограниченность проверок - Ручной отсев падающих кейсов

Slide 91

Slide 91 text

Здесь мы запускаем тесты Тестируемый сервис API Схема

Slide 92

Slide 92 text

? Тестируемый сервис API Ожидание

Slide 93

Slide 93 text

Сервис 4 Еще какой-то сервис ? Сервис 3 Сервис 5 Сервис 2 Тестируемый сервис Сервис 1 API ... Реальность

Slide 94

Slide 94 text

Еще какой-то фейковый сервис ? Фейковый сервис 3 Фейковый сервис 5 Фейковый сервис 2 Тестируемый сервис Фейковый сервис 1 API ...

Slide 95

Slide 95 text

Формируем экосистему Подходы к тестированию API

Slide 96

Slide 96 text

Результат теста Анализ статуса фейкового сервиса Запрос Формируем экосистему

Slide 97

Slide 97 text

@Rule public WireMockRule wiremock = new WireMockRule(LOCAL_MOCKED_PORT); @Test public void shouldSend3Callbacks() throws Exception { stubFor(any(urlMatching(".*")).willReturn(aResponse() .withStatus(HttpStatus.OK_200).withBody("OK"))); // --------------------------------------------------- // Здесь та магия, которая инициирует общение сервисов // --------------------------------------------------- verify(3, postRequestedFor(urlMatching(".*callback.*")) .withRequestBody(matching("^status=.*"))); }

Slide 98

Slide 98 text

// Создаем рулу для запуска и остановки пустого сервиса @Rule public WireMockRule wiremock = new WireMockRule(LOCAL_MOCKED_PORT); @Test public void shouldSend3Callbacks() throws Exception { stubFor(any(urlMatching(".*")).willReturn(aResponse() .withStatus(HttpStatus.OK_200).withBody("OK"))); // --------------------------------------------------- // Здесь та магия, которая инициирует общение сервисов // --------------------------------------------------- verify(3, postRequestedFor(urlMatching(".*callback.*")) .withRequestBody(matching("^status=.*"))); }

Slide 99

Slide 99 text

@Rule public WireMockRule wiremock = new WireMockRule(LOCAL_MOCKED_PORT); @Test public void shouldSend3Callbacks() throws Exception { // Настраиваем фейк-сервис принимать любые сообщения stubFor(any(urlMatching(".*")).willReturn(aResponse() .withStatus(HttpStatus.OK_200).withBody("OK"))); // --------------------------------------------------- // Здесь та магия, которая инициирует общение сервисов // --------------------------------------------------- verify(3, postRequestedFor(urlMatching(".*callback.*")) .withRequestBody(matching("^status=.*"))); }

Slide 100

Slide 100 text

@Rule public WireMockRule wiremock = new WireMockRule(LOCAL_MOCKED_PORT); @Test public void shouldSend3Callbacks() throws Exception { stubFor(any(urlMatching(".*")).willReturn(aResponse() .withStatus(HttpStatus.OK_200).withBody("OK"))); // --------------------------------------------------- // Здесь та магия, которая инициирует общение сервисов // --------------------------------------------------- verify(3, postRequestedFor(urlMatching(".*callback.*")) .withRequestBody(matching("^status=.*"))); }

Slide 101

Slide 101 text

@Rule public WireMockRule wiremock = new WireMockRule(LOCAL_MOCKED_PORT); @Test public void shouldSend3Callbacks() throws Exception { stubFor(any(urlMatching(".*")).willReturn(aResponse() .withStatus(HttpStatus.OK_200).withBody("OK"))); // --------------------------------------------------- // Здесь та магия, которая инициирует общение сервисов // --------------------------------------------------- // Обращаясь к логу фейк-сервиса, убеждаемся в наличии нужных сообщений verify(3, postRequestedFor(urlMatching(".*callback.*")) .withRequestBody(matching("^status=.*"))); }

Slide 102

Slide 102 text

+ Ограничиваем спецификацию + Может помочь в других подходах + Воспроизводим любое поведение сторонних сервисов - Довольно сложен в применении - Требуются знания о внутренней логики сервиса

Slide 103

Slide 103 text

Зачем это нужно? Первый автотест Инструменты Подходы Советы и полезные практики План лекции

Slide 104

Slide 104 text

https://github.com/allure-framework Allure Отчет

Slide 105

Slide 105 text

@Step("Get request with params param={0}" + "¶m2={1}¶m3={2}") public void getSomeRequest(String param, int param1, boolean param3) { //тут код запроса } @Step @Step

Slide 106

Slide 106 text

//Логирование в retrofit (отдельная зависимость) HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging).build(); /*--------------------------------------------------*/ //Логирование в rest-assured given().log().all(). и т.д. Логирование

Slide 107

Slide 107 text

@Attach

Slide 108

Slide 108 text

Скорость

Slide 109

Slide 109 text

@Test public void badTest() { Thread.sleep(5000); //то чего ждем } /* Особо запущенный случай */ @Test public void veryBadTest() { Thread.sleep(10000); //то чего ждем Thread.sleep(20000); //какая-то проверка логики Thread.sleep(30000); //то чего ждем повторно Thread.sleep(40000); //еще какая-то проверка логики Thread.sleep(50000); } Не использовать Thread.sleep в тестах

Slide 110

Slide 110 text

@Test public void goodTest() { int timeout = 10000; int interval = 1000; /* Каждую секунду делаем проверку до исчении таймаута 10 секунд */ assertThat(hc, withWaitFor(shoudBeBlah(), timeout, interval)); } Используйте матчер декоратор withWaitFor (htmlelements)

Slide 111

Slide 111 text

Надежность

Slide 112

Slide 112 text

● Формируйте тестовые данные через стабильную среду

Slide 113

Slide 113 text

Читаемость

Slide 114

Slide 114 text

/* Еще сойдет */ public class Test extends BaseTest { } /* Интересно, а что будет дальше? */ public class BaseTest extends BaseBaseTest { } /* Эм, а что было вначале? */ public abstract class BaseBaseTest extends BaseBaseBaseTest { } И так далее... Старайтесь не использовать наследования ● в тестовых классах

Slide 115

Slide 115 text

Поддержка

Slide 116

Slide 116 text

@Test public void badTest() { //тут основная логика теста String content = response.returnContent() .asString(); //и вдруг! какая-то бешенная регулярка String regExp = ""^\\{.*\"param\": " + "\"[a-z0-9_-]{2,13}\", " + "\"param1\": " + "\"[0-9]{3,15}\"," + "blah blah blah*\\}"; Pattern p = Pattern.compile(regExp); Matcher m = p.matcher(content); assertThat(m.matches(), equalTo(true)); } Никогда не работайте со строками!

Slide 117

Slide 117 text

{ "param": "blah" } @Generated("org.jsonschema2pojo") @JsonPropertyOrder({"param"}) public class Example { @JsonProperty("param") private String param; //getters and setters @JsonProperty("param") public String getParam() { return param; } @JsonProperty("param") public void setParam(String param) { this.param = param; } } JSON OBJECT

Slide 118

Slide 118 text

public class Resp { private String param1; private int param2; //getters and setters... } @Test public void goodTest() { //тут основная логика теста... String content = response.returnContent() .asString(); //получаем объект, с которым удобно работать ObjectMapper mapper = new ObjectMapper(); WeatherResp obj = mapper.readValue(entity, Resp.class); //работаем с этим объектом assertThat(obj.getParam(), equalTo("blah")); assertThat(obj.getParam1(), equalTo(1)); } Работайте с объектами!

Slide 119

Slide 119 text

//retrofit (из коробки) public interface MyService { @GET("/api/weather") Call weather(@Query("city") String city); }; /*--------------------------------------------------*/ // rest-assured given().baseUri("http://weather.lanwen.ru") .basePath("api").param("city", "Moscow").get("weather") .as(WeatherResp.class). и т.д. Преобразование к объекту

Slide 120

Slide 120 text

● Использовать рулы и матчеры

Slide 121

Slide 121 text

План лекции Зачем это нужно? Первый автотест Инструменты Подходы Советы и полезные практики

Slide 122

Slide 122 text

Спасибо за внимание!

Slide 123

Slide 123 text

Вопросы?