
Недавно нужно было написать API автотесты — запросы Post с большим количеством параметров в теле, в том числе вложенные JSON объекты, массивы , массивы JSON объектов. Многие параметры не обязательные, а значит — большое количество наборов тестовых данных.
Дано: Датапровайдер — для многократного запуска тестов, тестовые данные в таблице Excel.
Что бы сформировать тело первого запроса пришлось создать несколько классов, через сеттеры присваивать значения переменным в классе и из базового класса формировать JSON, который и использовался как тело запроса. И вдруг оказалось, что все написанное никак не получится переиспользовать для других эндпоинтов. Тогда и решил написать метод, который будет превращать таблицу Excel в JSON объект без всяких там классов и правок в коде. Нужно только придерживаться некоторых правил при составлении таблицы.
Итак! Для получения данных из таблицы использовал, как обычно, Fillo. Название столбца будет ключом, значения в столбце, собственно, значениями ключа в запросе. Строка таблицы — один набор тестовых данных.
Должно быть как-то так на входе:
|
shop_name |
сity |
|
shop#1 |
Charkiv |
|
shop#2 |
Kyiv |
|
shop#3 |
Dnipro |
На выходе:
[{"shop_name": "shop#1", "сity": "Charkiv"}, {"shop_name": "shop#2", "сity": "Kyiv"}, {"shop_name": "shop#3", "сity": "Dnipro"}]
Для начала, метод для получения ВСЕХ данных из Excel листа в виде массива мапов. Раньше использовал для похожих задач для вебтестов. Метод принимает две строки: путь к Excel файлу и название листа. Это единственные константы, которые вам придется захардкодить, все остальное управление формированием тела запроса выполняется из Excel файла. Я создаю для этого класс констант и там, если необходимо, меняю значение, которое потом используется во всех тестах.
Hidden text
public List fromExcelToListOfMaps (String docPath, String sheet) { PATH_TO_EXCEL_DOC = docPath; Fillo fillo = new Fillo(); Connection connection; List<Map<String, String>> listOfMaps = new ArrayList<>(); try { /* getting all the data from excel file */ connection = fillo.getConnection(PATH_TO_EXCEL_DOC); Recordset recordset = connection.executeQuery("Select * From " +sheet+" "); /* getting column names*/ ArrayList<String> keys = recordset.getFieldNames(); int size = keys.size(); /* creating array of maps*/ while (recordset.next()) { Map<String, String> values = new LinkedMap<>(); for (int i = 0; i <size; i++) { String key = keys.get(i); String value = recordset.getField(key); values.put(key, value); } listOfMaps.add(values); } recordset.close(); connection.close(); } catch (FilloException e) { e.printStackTrace(); } return listOfMaps; }
результат:
[{shop_name=shop#1, сity=Charkiv}, {shop_name=shop#2, сity=Kyiv}, {shop_name=shop#3, сity=Dnipro}]
Превратить мапу в JSON с помощью GSON можно за одно действие, но тут оказалось, что сервер не принимает пустые ключи, поэтому их нужно выпилить в процессе. Следующий метод принимает на вход массив из метода fromExcelToListOfMaps , удаляет ключи без значений и возвращает массив JSON строк.
public List<String> dataToListOfJson(List<Map<String, String>> listOfMaps){ Gson gson = new Gson(); List<String> listOfJson = new ArrayList<>(); for (int i=0; i<listOfMaps.size(); i++) { /* * getting each map collection * */ Map<String,String> row = listOfMaps.get(i); /* * deleting empty values */ row.entrySet().removeIf(y -> (y.getValue().equals(""))); /* * Turn map without empty fields to json */ String requestBody = gson.toJson(row); listOfJson.add(requestBody); } return listOfJson; }
На входе:
|
shop_name |
сity |
|
shop#1 |
Charkiv |
|
Kyiv |
|
|
shop#3 |
После первого метода:
[{shop_name=shop#1, сity=Charkiv},
{shop_name=, сity=Kyiv},
{shop_name=shop#3, сity=}]
На выходе:
[{«shop_name»:»shop#1″,»сity»:»Charkiv»},
{«сity»:»Kyiv»},
{«shop_name»:»shop#3″}]
В таком виде уже можно отправлять результат в датапровайдер, но у меня значения не только типа String. А как объяснить методу, что вот это вот boolean, а вот это Integer и кавычки тут не нужны, а там вообще массив JSON объектов. И в коде указывать нельзя, получится не универсально. Решил в название столбцов добавить ключевые слова:
-
string — нет ключевого слова
-
boolean — bool
-
float — flo
-
int — int
Ключевое слово отделяется от имени ключа двумя звездочками (**). Теперь, после проверки на пустые значения, выполняется проверка на наличия ключевого слова в названии столбца, и, если это слово есть и соответствует одному из приведенных выше, создается новый ключ (отбрасывается ключевое слово и звездочки) и ему присваивается значение с приведением к нужному типу данных. Выглядит примерно так:
public static final String FLOAT_VAL = "flo"; public static final String BOOLEAN_VAL = "bool"; /*work with Float*/ else if (keyWord.equals(FLOAT_VAL)){ value = value.replace(",", "."); JsonElement a = gson.toJsonTree(Float.parseFloat(value)); jsElemMap.add(key.substring(x+2), a); } /*work with Boolean*/ else if (keyWord.equals(BOOLEAN_VAL)){ JsonElement a = gson.toJsonTree(Boolean.parseBoolean(value)); jsElemMap.add(key.substring(x+2), a);
после чего из мапы выпиливаются все пары в ключе которых присутствуют ** . Т.е. если написать в таблице ключевое слово с ошибкой, то эта колонка в запрос не попадет.
В результате
На входе:
|
shop_name |
сity |
Int**employee_qty |
Bool**Sunday_off |
|
shop#1 |
Charkiv |
3 |
true |
|
shop#2 |
Kyiv |
3 |
true |
|
shop#3 |
Dnipro |
2 |
false |
на выходе:
[{"shop_name":"shop#1","сity":"Charkiv","employee_qty":3,"Sunday_off":true}, {"shop_name":"shop#2","сity":"Kyiv","employee_qty":3,"Sunday_off":true}, {"shop_name":"shop#3","сity":"Dnipro","employee_qty":2,"Sunday_off":false}]
Конечно работать с разными типами данных в Map<String, String> не получится, поэтому все результаты пересобираются в переменную типа JsonObject.
Дальше нужно было добавить ключи со значениями класса JsonObject. Конечно можно просто в Excel ячейку записать что-то типа {«salary»: 25.0,»bonus»: 4.75}, но ведь у меня уже есть метод, который который соберет JSON из листа Excel. Нужно только указать лист и строку которая будет использоваться. Получилось чуть-чуть рекурсии. Ключевое слово:
-
JsonObject — Если в качестве значения ключа нужен JsonObject, то ключевое слово должно совпадать с названием Excel листа, из которого берутся данные (добавил метод получающий все имена листов Excel файла в массив). Значение в ячейке (тип int) — номер строки в этом листе (нумерация с нуля).
/*work with JsonObjects */ /*If Excel file contains sheet which name equals keyWord */ else if (excelSheetNames(PATH_TO_EXCEL_DOC).contains(keyWord)){ /*get this sheet as list of json*/ List aa = dataToListOfJson2(fromExcelToListOfMaps(PATH_TO_EXCEL_DOC,keyWord)); /*and add one of json as value of current key*/ jsElemMap.add(key.substring(x+2), gson.toJsonTree(aa.get(Integer.parseInt(value)))); }
В результате на входе:

На выходе:
[{"Id":0,"First_Name":"Wasja","Last_Name":"Smith","Age":19,"Gender":"m","paycheck":{"salary":20.0}}, {"Id":1,"First_Name":"Tanja","Last_Name":"Johnson","Age":25,"Gender":"f","paycheck":{"salary":20.0}}, {"Id":250,"First_Name":"Ighor","Last_Name":"Williams","Age":42,"Gender":"m","paycheck":{"salary":25.0,"bonus":4.75}}, {"Id":13,"First_Name":"Masha","Last_Name":"Brown","Age":19,"Gender":"f","paycheck":{"salary":25.0,"bonus":4.75}}, {"Id":4,"First_Name":"Olja","Last_Name":"Davis","Age":18,"Gender":"f","paycheck":{"salary":33.3,"bonus":5.8}}, {"Id":40,"First_Name":"Oleg","Last_Name":"Miller","Age":25,"Gender":"m","paycheck":{"bonus":100.0}}, {"Id":6,"First_Name":"Kolja","Last_Name":"Wilson","Age":21,"Gender":"m","paycheck":{"salary":33.3,"bonus":5.8}}, {"Id":7,"First_Name":"Andrew","Last_Name":"Moore","Age":20,"Gender":"m","paycheck":{"salary":25.0,"bonus":4.75}}]
Остались массивы. Наверное, стоило написать логику с нуля, но я поленился и решил из JsonObject и выбранной строки просто все значения перенести в List<Object> . Написал отдельный метод. На вход принимает путь к Excel файлу, имя листа и номер строки (нумерация с нуля)
public List arrayVal(String pathToExcel, String sheetName, int rowNumber){ List<JsonObject> x = dataToListOfJson2(fromExcelToListOfMaps(pathToExcel, sheetName)); JsonObject dataForArray = x.get(rowNumber); List<Object> arr= new LinkedList<>(); for(String key : dataForArray.keySet()){ arr.add(dataForArray.get(key)); } return arr; }
Ключевое слово:
-
List<Object> — arr. Имя столбца должно совпадать с именем Excel листа, из которого будут браться значения для массива. Значение в ячейке (тип int) — номер строки. Имя столбцов в Excel листе из которого будут набираться данные в массив значения не имеют, но они не должны повторяться (Fillo не обработает) и для всех столбцов должно быть одинаковое ключевое слово. (для данных типа String ключевое слово не нужно).
В результате как-то так на входе:

На выходе:
[{"shop_name":"shop#1","сity":"Charkiv","income_per_month":[78500,22222,2222,159]}, {"shop_name":"shop#2","сity":"Kyiv","income_per_month":[56900,11111,987456,6423,98741]}, {"shop_name":"shop#3","сity":"Dnipro","income_per_month":[12694,33333,111111]}]
Вот основной класс с методами:
Hidden text
package api; import com.codoid.products.exception.FilloException; import com.codoid.products.fillo.Connection; import com.codoid.products.fillo.Fillo; import com.codoid.products.fillo.Recordset; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.commons.collections4.map.LinkedMap; import java.util.*; import static api.ApiConstants.ApiBodyConstants.*; import static jdk.nashorn.internal.objects.NativeString.toLowerCase; public class ApiBase { public static String PATH_TO_EXCEL_DOC = ""; public List fromExcelToListOfMaps (String docPath, String sheet) { PATH_TO_EXCEL_DOC = docPath; Fillo fillo = new Fillo(); Connection connection; List<Map<String, String>> listOfMaps = new ArrayList<>(); try { /* getting all the data from excel file */ connection = fillo.getConnection(docPath); Recordset recordset = connection.executeQuery("Select * From " +sheet+" "); /* getting column names*/ ArrayList<String> keys = recordset.getFieldNames(); int size = keys.size(); /* creating array of maps*/ while (recordset.next()) { Map<String, String> values = new LinkedMap<>(); for (int i = 0; i <size; i++) { String key = keys.get(i); String value = recordset.getField(key); values.put(key, value); } listOfMaps.add(values); } recordset.close(); connection.close(); } catch (FilloException e) { e.printStackTrace(); } return listOfMaps; } public List<String> dataToListOfJson(List<Map<String, String>> listOfMaps){ Gson gson = new Gson(); List<String> listOfJson = new ArrayList<>(); for (int i=0; i<listOfMaps.size(); i++) { /* * getting each map collection * */ Map<String,String> row = listOfMaps.get(i); /* * deleting empty values */ row.entrySet().removeIf(y -> (y.getValue().equals(""))); /*deleting empty values in a longer way*/ // for (Map.Entry<String, String> entry: row.entrySet()) { // String key = entry.getKey(); // String value = entry.getValue(); // if(value.equals("")){ // row.remove(key); // } // } /* * Turn map without empty fields to json */ String requestBody = gson.toJson(row); listOfJson.add(requestBody); } return listOfJson; } public List<JsonObject> dataToListOfJson2(List<Map<String, String>> listOfMaps){ Gson gson = new Gson(); List<JsonObject> listOfJson = new ArrayList<>(); for (int i=0; i<listOfMaps.size(); i++) { /* * getting each map collection * */ Map<String,String> row = listOfMaps.get(i); JsonObject jsElemMap = new JsonObject(); /* * deleting keys that have empty values */ row.entrySet().removeIf(y -> (y.getValue().equals(""))); /*iteration map to change value classes and add them to json object */ for (Map.Entry<String, String> entry: row.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); if (key.indexOf("**")>0) { int x = key.indexOf("**"); String keyWord = toLowerCase(key.substring(0, x)); /*work with Integer*/ if (keyWord.equals(INT_VAL)){ JsonElement a = gson.toJsonTree(Integer.parseInt(value)); jsElemMap.add(key.substring(x+2), a); } /*work with Float*/ else if (keyWord.equals(FLOAT_VAL)){ value = value.replace(",", "."); JsonElement a = gson.toJsonTree(Float.parseFloat(value)); jsElemMap.add(key.substring(x+2), a); } /*work with Boolean*/ else if (keyWord.equals(BOOLEAN_VAL)){ JsonElement a = gson.toJsonTree(Boolean.parseBoolean(value)); jsElemMap.add(key.substring(x+2), a); } /*work with array*/ else if (keyWord.equals(ARRAY_VAL)&& excelSheetNames(PATH_TO_EXCEL_DOC).contains(key.substring(x+2))){ List arr = arrayVal(PATH_TO_EXCEL_DOC, key.substring(x+2), Integer.parseInt(value)); JsonElement a = gson.toJsonTree(arr); jsElemMap.add(key.substring(x+2), a); } /*work with JsonObjects */ /*If Excel file contains sheet which name equals keyWord */ else if (excelSheetNames(PATH_TO_EXCEL_DOC).contains(keyWord)){ /*get this sheet as list of json*/ List aa = dataToListOfJson2(fromExcelToListOfMaps(PATH_TO_EXCEL_DOC,keyWord)); /*and add one of json as value of current key*/ jsElemMap.add(key.substring(x+2), gson.toJsonTree(aa.get(Integer.parseInt(value)))); } }else{ /*If there is no keyWord*/ jsElemMap.add(key, gson.toJsonTree(value) ); } } /* remove all keys with keyword */ row.entrySet().removeIf(y -> (y.getKey().contains("**"))); /* * Turn map without empty fields to json */ listOfJson.add(jsElemMap); } return listOfJson; } public List arrayVal(String pathToExcel, String sheetName, int rowNumber){ List<JsonObject> x = dataToListOfJson2(fromExcelToListOfMaps(pathToExcel, sheetName)); JsonObject dataForArray = x.get(rowNumber); List<Object> arr= new LinkedList<>(); for(String key : dataForArray.keySet()){ arr.add(dataForArray.get(key)); } return arr; } /*getting names of sheets to list*/ public List<String> excelSheetNames(String filePath){ Fillo fillo = new Fillo(); Connection connection =null; List<String> names = new ArrayList<>(); try { connection = fillo.getConnection(filePath); names = connection.getMetaData().getTableNames(); connection.close(); } catch (FilloException filloException) { connection.close(); filloException.printStackTrace(); } return names; } }
В общем по функционалу на этом все, осталось заполнить таблицу. Оказалось, что указывать номера строк, находящихся на другом листе, не очень-то и удобно, постоянно какая-то путаница. Тут мне на помощь пришли неправильные ключевые слова. Если в имени столбца присутствует две звездочки (**), но текст перед ** не соответствует одному из ключей, то этот столбец в запросе игнорируется, и в него можно писать комментарии, добавлять нумерацию, или выводить результаты теста. Я ставил четыре звездочки, чтобы сходу видеть, что в запрос не попадет. Таблицы стали выглядеть как-то так:
|
int**Id |
First_Name |
Last_Name |
int**Age |
Gender |
pay**paycheck |
com****coment |
|
0 |
Wasja |
Smith |
19 |
m |
0 |
<— Point off row****number from the sheet pay |
|
1 |
Tanja |
Johnson |
25 |
f |
0 |
|
|
2 |
Ighor |
Williams |
42 |
m |
1 |
|
|
3 |
Masha |
Brown |
19 |
f |
1 |
|
|
4 |
Olja |
Davis |
18 |
f |
3 |
|
|
5 |
Oleg |
Miller |
25 |
m |
4 |
|
|
6 |
Kolja |
Wilson |
21 |
m |
3 |
|
|
7 |
Andrew |
Moore |
20 |
m |
1 |
|
row****numb |
flo**salary |
flo**bonus |
|
0 |
20 |
|
|
1 |
25 |
4,75 |
|
2 |
22 |
4 |
|
3 |
33,3 |
5,8 |
|
4 |
100 |
Осталось передать все это в датапровайдер и превратить в классический двухмерный массив строк.
@DataProvider(name = "Request2") public Object[][] request2() { List<JsonObject> listOfJson = base.dataToListOfJson2(base .fromExcelToListOfMaps(PATH_TO_EXCEL_DOC,SHEET_NAME)); String[][] arrayOfJson = new String[listOfJson.size()][1]; for(int i = 0; i< listOfJson.size(); i++){ arrayOfJson[i][0] = listOfJson.get(i).toString(); } return arrayOfJson; }
И дальше в тест:
@Test(dataProvider = "Request2") public void exampleTest1_3 (String request){ given() .spec(requestPostEvents(request)) .when() .log().body() .post(); }
Тело первого теста:
Hidden text
Body:
{
«shop_name»: «shop#1»,
«сity»: «Charkiv»,
«income_per_month»: [
78500,
22222,
2222,
159
],
«employee_qty»: 3,
«employees»: [
{
«Id»: 0,
«First_Name»: «Wasja»,
«Last_Name»: «Smith»,
«Age»: 19,
«Gender»: «m»,
«paycheck»: {
«salary»: 20.0
}
},
{
«Id»: 1,
«First_Name»: «Tanja»,
«Last_Name»: «Johnson»,
«Age»: 25,
«Gender»: «f»,
«paycheck»: {
«salary»: 20.0
}
},
{
«Id»: 2,
«First_Name»: «Ighor»,
«Last_Name»: «Williams»,
«Age»: 42,
«Gender»: «m»,
«paycheck»: {
«salary»: 25.0,
«bonus»: 4.75
}
}
],
«work_time»: «09,00-18,00»,
«Sunday_off»: true,
«goods»: {
«tobacco»: true,
«alcohol»: true,
«groshery»: true,
«weapons»: false,
«drugs»: false
}
}
Теперь, в случае если будут добавлены, удалены или изменены параметры, или тестовые данные, мне даже не нужно будет заглядывать в код, только подправить табличку. Как и все универсальное, в обращении не очень удобно, но привыкнуть можно.
Все это писалось в отдельном проекте, клонировать можно здесь.
Можно подправить файл Data.xlsx под ваши нужды и позапускать тесты из класса ExampleTests , что бы посмотреть, получите ли вы то, что вам нужно. Если вы начинаете новый проект с API (+WEB) тестами, это можно использовать как шаблон.
Если нужно применить в существующем проекте, но совсем не хочется разбираться как это устроено, то скопируйте пакеты api и файл Data.xlsx в соответствующие разделы вашего проекта на Maven + TestNG, при необходимости добавьте нужные зависимости и пишите тесты в классе ExampleTests.

Собираюсь потихоньку перетянуть сюда все универсальные методы, которые использовал — пусть все лежит в одном месте! Надеюсь, что информация поможет другим начинающим автоматизаторам).
ссылка на оригинал статьи https://habr.com/ru/post/658763/
Добавить комментарий