Из Excel в JSON. Универсальные методы для формирования тела запроса из Excel книги для API тестов (Java)

от автора

Недавно нужно было написать 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 объектов. И в коде указывать нельзя, получится не универсально. Решил в название столбцов добавить ключевые слова:

  1. string — нет ключевого слова

  2. boolean — bool

  3. float — flo

  4. 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. Нужно только указать лист и строку которая будет использоваться. Получилось чуть-чуть рекурсии. Ключевое слово:

  1. 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;     }

Ключевое слово:

  1. 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *