julia_v_iluhina

Untitled

Oct 18th, 2016
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 58.29 KB | None | 0 0
  1. http://joxi.ru/8An6LoZhq5RKJA
  2.  
  3.   /*
  4.       1 - мы оперируем идентичными данными фактически
  5.  
  6.       Не надо в данном методе сервиса - дублировать коллекцию tasks
  7.       Нужно грамотно воспользоваться тем, что уже есть в коде
  8.       Если начальное состояние фиксировать в некой коллекции
  9.       default_tasks, при старте и ресете - копировать в  tasks содержимое default_tasks
  10.       В python есть способ  - скопировать содержимое одного списка в другой с помощью одной операции,
  11.       а не переносить каждый элемент списка отдельно
  12.  
  13.       Полезные линки
  14.       http://www.python-course.eu/python3_global_vs_local_variables.php
  15.       https://docs.python.org/2/library/copy.html
  16.       http://joxi.ru/Dr8vxzPFk3Kl32
  17.       http://stackoverflow.com/questions/2612802/how-to-clone-or-copy-a-list-in-python
  18.       http://joxi.ru/J2b9KdBt41qEpm
  19.  
  20.       2 - в новом методе можно воспользоваться уже существующим методом
  21.       get_tasks() (после того, как скопировать в  tasks содержимое default_tasks)
  22.   */
  23.  
  24.   http://joxi.ru/Y2LXgYnfnKLq02
  25. /*
  26.     вот еще источник проблем)
  27.  
  28.     если мы создаем первую таску (например, перед этим - все таски удалили)
  29.     то получим проблему)
  30.     не будет у нас предыдущего элемента)
  31.  
  32.     все же - если элементов в коллекции тасок нету - то у первой таски должен быть ид=1
  33.  
  34.     https://lancelote.gitbooks.io/intermediate-python/content/book/ternary_operators.html
  35.     https://lancelote.gitbooks.io/intermediate-python/content/book/comprehensions.html (dict абстракции)
  36.     http://www.diveintopython.net/native_data_types/index.html#odbchelper.dict
  37.  
  38. */
  39. *********************************************************
  40. public class RESTfulTasksTest {
  41. /*
  42.     по правилам CamelCase - будет не так)
  43.     https://google.github.io/styleguide/javaguide.html#s5.3-camel-case
  44. */
  45.     TasksPage page = new TasksPage();
  46.     /*
  47.         посмотри конец видео про этот момент
  48.         нам нужен не пейдж-объект
  49.         а ресурс-объект
  50.         примерно с  01:17:00
  51.  
  52.         Теперь у нас будет пекедж не pages, a resources.
  53.         А правила работы с таким классом - аналогичные.
  54.  
  55.         Поскольку мы работаем только с тасками - в имена методов-действий можно не включать слово Tasks
  56.         Имена методов-проверок - по-прежнему указываем точно
  57.  
  58.         Логично было бы назвать класс TasksApi
  59.         (можно реализовывать как ресурс-модуль, а не ресурс-объект - как ты помнишь -
  60.         таким образом код может стать немного проще)
  61.  
  62.         И в коде потом можно без статического импорта писать
  63.                 Response response = TasksApi.create(URI, "give lesson");
  64.  
  65.  
  66.         Это было бы и наглядно, и с прицелом на будущее - на случай,
  67.         если в тесте появятся запросы к другим ресурсам (и тогда будет несколько ресурс-модулей)
  68.  
  69.     */
  70. ********************************************************************
  71. public class TasksPage {
  72.     public static final String URI = "http://localhost:5000/todo/api/v1.0/tasks";
  73.     /*
  74.         Еще было бы полезно вынести http://localhost:5000
  75.         в Сonfiguration.baseUrl - в котором именно домен был сохранен
  76.         чтобы его можно было от случая к случаю реконфигурить...
  77.         Ведь это как раз та часть урла - которая не принадлежит ресурсу, и зависит от того,
  78.         на каком сервере/по-какому-адресу ресурс/веб-сервис задеплоен
  79.  
  80.         На самом деле здесь есть несколько способов как лучше сделать
  81.  
  82.         Первый, если ты уверена, что тебе не нужно будет одновременно/паралельно посылать запросы на разные урлы
  83.         То тогда можно использувать Configuration.baseUrl типа обычная глобальная переменная
  84.  
  85.         если нет, тогда лучше "ооп" подход, когда обьект-ресурс помнит свой базовый урл (доменную часть)
  86.         который передается при создании ресурса-объекта через конструктор
  87.     */
  88. *************************************
  89.  
  90.     public static final List<Task> defaultTasks = new ArrayList<Task>();
  91.     /*
  92.         как и в пейджах, так и в ресурсах - мы не будем держать тестовых данных
  93.         и контейнеров под них = тоже не будем создавать на уровне ресурса
  94.         тут действительно - будет полезным вынести такие данные в переменные - т к
  95.         они всегда одни и те же и достаточно громоздкие. Но делать это нужно не на уровне ресурса
  96.  
  97.         С таким вынесением тестовых данных в переменные надо быть осторожным
  98.  
  99.         Важно - не надо тестовые данные объявлять в ресурс модуле
  100.         (опять аналогия с пейджом - мы и там такого правила придерживались)
  101.         Тут и правда есть смысл объявить эти данные
  102.  
  103.         Часто тестовые данные разумно оставлять в логике самих тестов -
  104.         таким образом код тестов более очевидный. Пример - тудуэмвиси тесты
  105.  
  106.     */
  107. ********************************************************
  108.     public Invocation.Builder requestTo(String uri) {
  109.         return ClientBuilder.newClient().target(uri).request();
  110.     }
  111.  
  112.     public Invocation.Builder authorized(Invocation.Builder requestBuilder) {
  113.         return requestBuilder.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("miguel:python".getBytes()));
  114.     }
  115.  
  116.     public Invocation.Builder authorizedRequestTo(String uri) {
  117.         return authorized(requestTo(uri));
  118.     }
  119.     /*
  120.         Вот эти методы действительно выполняют роль универсальных REST-хелперов -
  121.         и их действительно разумно отделить в специальный класс-контейнер универсальных метдов RestHelpers
  122.  
  123.         Единственное, что authorized -  стоит реализовать универсальнее -
  124.         чтобы он получал логін:пароль как параметр
  125.  
  126.         А потом уже в нашем ресурс модуле
  127.         создать метод authorizedRequest(...), который будет вызывать
  128.         return authorized(“miguel:python”, requestTo(...))
  129.  
  130.         Нужно такие нюансы видеть и выделять универсальный код, который не зависит от нашего продукта
  131.         Вот такой универсальный код принято размещать в пекедже core
  132.  
  133.     */
  134. ********************************
  135.     public Response sendGetRequest(String uri) {
  136.         return requestTo(uri).get();
  137.     }
  138.  
  139.     public Response sendAuthorizedGetRequest(String uri) {
  140.         return authorized(requestTo(uri)).get();
  141.     }
  142.  
  143. /*
  144.     сравни вызовы
  145.  
  146.     sendGetRequest(uri);
  147.     requestTo(uri).get();
  148.  
  149.     sendAuthorizedGetRequest(uri);
  150.     authorized(requestTo(uri)).get();
  151.     authorizedRequestTo(uri).get(); - этот вариант получше - обрати на него внимание
  152.  
  153.     выигрыш - 1-2 символа
  154.     плюс к этому - добавили сущностей
  155.  
  156.     это как раз пример того - когда заворачивание кода в метод уже мало что дает
  157.     только сложность повышаем - т к с большим количеством методов придется разобраться
  158.  
  159.     прибей эти методы
  160.     и перейди на использование
  161.     requestTo(uri).get();
  162.     authorizedRequestTo(uri).get();
  163. */
  164. ********************************
  165.     public Response resetTasks() {
  166.         return sendAuthorizedGetRequest(URI + "/reset");
  167.     }
  168. /*
  169.     поскольку не используешь результат метода - можно его реализовать и как
  170.     public static void reset()
  171.     (про употребление в именах методов-действий Task/Tasks - выше писала)
  172. */
  173. ************************************************
  174.     public List<Task> availableTasks() {
  175.         Response response = authorized(requestTo(URI)).get();
  176.         return response.readEntity(TasksContainer.class).getTasks();
  177.     }
  178. /*
  179.     что мы тут делаем - мы делаем get - для тасок
  180.     вот и стоит также метод назвать - get()
  181.  
  182.     не надо вводить лишних терминов
  183.  
  184.     код можно переписать в одну строку
  185.     return  authorizedRequestTo(URI).get().readEntity(TasksContainer.class).getTasks();
  186.  
  187.     мне кажется - все еще ок
  188.     это на твое усмотрение
  189. */
  190. ********************************************************
  191.     public Response sendAuthorizedPostRequest(String uri, Task task) {
  192.         return authorized(requestTo(uri)).post(Entity.entity(task, MediaType.APPLICATION_JSON));
  193.     }
  194. /*
  195.     что мы делаем - мы создает таску
  196.     причем - мы тут оперируем тем же URI
  197.  
  198.     упрощаем имя метода и набор параметров
  199.     public static Response create(Task task)
  200.     а если еще немного упростим -
  201.     public static Task create(Task task)
  202.  
  203.     Правильный статус ответа на каждый из запросов - строго определенный
  204.         И если мы будем проверять статут ответа в тест-методе -
  205.         то надо об этом думать и вспоминать - какой статус в каких случаях верный
  206.  
  207.         И можно выкрутиться - проверять статус ответа в нутри степа,
  208.         а возвращать собственно entity из запроса.
  209.         Сразу пара потенциально полезных моментов)
  210.  
  211.     Если встроить проверку response кода прямо в сам степ
  212.     Тут сразу появляется интересный нюанс
  213.     Дело в том, что мы так спрячем тестовую логику внутрь степа, что не очень хорошо
  214.     С другой стороны - эта проверка = само собой разумеющееся
  215.     Эта проверка достаточно стандартная и всегда будет повторяться после вызова степа (если будет жить вне степа)
  216.     А раз так - то почему бы и не спрятать?
  217.     На самом деле - нет однозначного ответа на этот вопрос...
  218.     Так - со спрятанной проверкой - код станет проще во всех тестах - но втанет до некоторой степени “магическим”
  219.     Тут надо решать самостоятельно - как лучше )
  220.     На твое усмотрение
  221.     Еще момент
  222.     В этих API тестах - самих тестов может быть не так уж и много -
  223.     чтоб еще и заморачиваться над DRY кода...нужно и этот аспект учитывать
  224.  
  225.     Но если пойти этим путем = прятать в степе проверку статуса response, то код будет намного более удобным:
  226. */
  227.         @Test
  228.         public void testCreate() {
  229.             Response response = Tasks.create("give lesson");
  230.  
  231.             assertEquals(201, response.getStatus());
  232.             assertEquals("give lesson", titleFor(response));
  233.         }
  234.  
  235.     vs
  236.  
  237.         @Test
  238.         public void testCreate() {
  239.             Task task = Tasks.create("give lesson");
  240.  
  241.             assertEquals("give lesson", task.getTitle());
  242.         }
  243. **********************************************
  244.     public Response sendAuthorizedPutRequest(String uri, Task task) {
  245.         return authorized(requestTo(uri)).put(Entity.entity(task, MediaType.APPLICATION_JSON));
  246.     }
  247. /*
  248.     если и тут применить те же приемы - получим
  249.  
  250.     public static Task update(Task task)
  251.  
  252.     согласись - понятнее - что мы будем делать
  253.  
  254.     что до того - где брать uri - task.getUri() нам его вполне отдаст )
  255. */
  256. *************************************
  257.     public Response sendAuthorizedDeleteRequest(String uri) {
  258.         return authorized(requestTo(uri)).delete();
  259.     }
  260. /*
  261.     можно вот так реализовать метод
  262.     public static void deleteById(int taskId)
  263.  
  264.     тут на твой выбор - что передавать в качестве параметра
  265.     нам по мольшому счету нужен лишь uri таски
  266.     а значит - лишь ID таски
  267.  
  268.     но можно,конечно и Task task, и String uri
  269.     параметр реализовать
  270.  
  271.     если внутри метода проверять код ответа - то уже не нужно ничего возвращать
  272. */
  273. *********************************************************
  274.     public List<Task> tasksFromResponse(Response response) {
  275.         return response.readEntity(TasksContainer.class).getTasks();
  276.     }
  277.  
  278.     public Task taskFromResponse (Response response) {
  279.         return response.readEntity(TaskContainer.class).getTask();
  280.     }
  281. /*
  282.     как ты понимаешь, и эти методы потеряют актуальность -
  283.     т к все результаты - ты получаешь из респонса уже в рамках методов-действий
  284.  
  285.     еще чуть проще код стал)
  286. */
  287. ***********************************************
  288.  public void assertStatus(int statusCode, Response response) {
  289.         assertEquals(statusCode, response.getStatus());
  290.  }
  291.  
  292.  /*
  293.     сравни
  294.     assertEquals(statusCode, response.getStatus())
  295.     assertStatus(statusCode, response)
  296.  
  297.     я не уверена - что такой метод нужен )
  298.     если ты видишь в нем смысл
  299.     то перенеси и его в RestHelpers
  300.  
  301.     по-моему - перебор)
  302.  */
  303. *********************************************
  304.     public List<Task> joinTasks(Task... tasksToJoin) {
  305.     public List<Task> joinTasks(List<Task> tasks, Task... tasksToJoin) {
  306.     public void assertEqualListsOfTasks(List<Task> expectedTasks, List<Task> actualTasks) {
  307.     public void assertEqualTasks(Task expected, Task actual) {
  308.  
  309.     @Test
  310.     public void testCreateTaskWithTitle() {
  311.         ...
  312.  
  313.         page.assertStatus(201, response);
  314.         page.assertEqualTasks(newTask, page.taskFromResponse(response));
  315.  
  316.         List<Task> expectedTasks = page.joinTasks(defaultTasks, newTask);
  317.         page.assertEqualListsOfTasks(expectedTasks, page.availableTasks());
  318.     }
  319.  /*
  320.      посмотри на информацию по этим линкам
  321.      http://www.java2s.com/Tutorial/Java/0140__Collections/CheckingforEquality.htm
  322.      http://www.javapractices.com/topic/TopicAction.do?Id=17
  323.      http://javadevnotes.com/java-array-to-list-examples
  324.  
  325.      мы можем получить код проще
  326.      public static void assertTasks(Task... expectedTasks) {
  327.              List<Task> actualTasks = get();
  328.              assertEquals(Arrays.asList(expectedTasks), actualTasks);
  329.      }
  330.  
  331.      А если реализуешь метод toString для класса Task - и сообщение об ошибке будет более информативным.
  332.  
  333.      ну и если говорить о реализации проверок в testCreateTaskWithTitle()
  334.      то можно ограничиться проверкой assertTasks
  335.  
  336.      и не проверять таску, полученную из респонса
  337.      хотя, конечно, это тоже сильно зависит от многого
  338.      даже если и надо проверять - assertEquals - и для одной таски будет ок работать,
  339.      если уже нормально для списка тасок работает)
  340.  
  341.  */
  342. *********************************************************************
  343.  
  344.  
  345.     @BeforeClass
  346.     public static void initTasks() {
  347.        //straight way to save our default tasks
  348.         Task firstTask = new Task("Buy groceries", "Milk, Cheese, Pizza, Fruit, Tylenol", false);
  349.         firstTask.setUri("http://localhost:5000/todo/api/v1.0/tasks/1");
  350.         Task secondTask = new Task("Learn Python", "Need to find a good Python tutorial on the web", false);
  351.         secondTask.setUri("http://localhost:5000/todo/api/v1.0/tasks/2");
  352.  
  353.         //List<Task> defaultTasks = new ArrayList<Task>();
  354.         defaultTasks.add(firstTask);
  355.         defaultTasks.add(secondTask);
  356.  
  357.        //tried this way also but got an error from IDEA
  358. //       List<Task> defaultTasksNewWay = Arrays.asList(new Task("Buy groceries", "Milk, Cheese, Pizza, Fruit, Tylenol", false)
  359. //               .setUri("http://localhost:5000/todo/api/v1.0/tasks/1"),
  360. //            new Task("Learn Python", "Need to find a good Python tutorial on the web", false).setUri("http://localhost:5000/todo/api/v1.0/tasks/2"));
  361.    }
  362. /*
  363.     можно проще
  364.     просто - объявить и инициализировать массив
  365.  
  366.     public final Task[] DEFAULT_TASKS = {
  367.                 new Task("Buy groceries", "Milk, Cheese, Pizza, Fruit, Tylenol", false, TasksApi.uri + "/1"),
  368.                 new Task("Learn Python", "Need to find a good Python tutorial on the web", false, TasksApi.uri + "/2")
  369.     };
  370.  
  371.     и нагляднее, и никаких методов и шагов
  372.  
  373. */
  374.     @Test
  375.     public void testResetTasks() {
  376.         Response response = page.resetTasks();
  377.  
  378.         page.assertStatus(200, response);
  379.         page.assertEqualListsOfTasks(defaultTasks, page.tasksFromResponse(response));
  380.     }
  381. /*
  382.     а есть ли смысл в таком тест-методе
  383.  
  384.     если
  385.         ресет делать в бифор-методе
  386.         и реализовать один из тестов - для get() = получить таски
  387.  
  388.         то мы как раз проверим и reset, и get
  389.         до некоторой степени - не важно - что за таски нам будут переданы в респонсе ресета
  390.         главное - чтобы ресет происходил = в базе были именно такие таски = таски по умолчанию
  391.  
  392.         вот потому - можно и не реализовывать тест для ресета)
  393. */
  394. ***************************************
  395.     @Test
  396.     public void testReadTasks() {
  397.     /*
  398.         и тут в именах тест-методов можно сэкономить на слове Tasks
  399.         с оглядкой на имя тест-класса)
  400.     */
  401.         page.resetTasks();
  402.     /*
  403.         этот гивен(предварительное действие) = уйдет в бифор метод
  404.  
  405.         этот гивен на самом деле такой важный, что лучше вообще не скрывать его
  406.         (в классе-предке так точно не стоит)
  407.  
  408.         его желательно "видеть" в самом тесте
  409.  
  410.         часто даже не стоит такие вещи в @Before выносить, не смотря на то что код будет не DRY
  411.         но будет еще более явным и очевидным)
  412.  
  413.         Хотя в этом случае наверное с @Before будет таки лучше )
  414.         Тестов не много, они лаконичные
  415.         @Before-метод размести в начале тест-класса - будет достаточно наглядно
  416.     */
  417.  
  418.         Response response = page.sendAuthorizedGetRequest(URI);
  419.  
  420.         page.assertStatus(200, response);
  421.         /*
  422.             код ответа мы уже проверяем в методе get
  423.         */
  424.         page.assertEqualListsOfTasks(defaultTasks, page.tasksFromResponse(response));
  425.     }
  426. /*
  427.     вот в результате таких доработок - и получим вариант полаконичнее
  428. */
  429.     @Test
  430.     public void testRead() {
  431.         List<Task> actualTasks = TasksApi.get();
  432.  
  433.         assertEquals(Arrays.asList(DEFAULT_TASKS), actualTasks);
  434.     }
  435. *****************************************
  436.  
  437.     @Test
  438.     public void testCreateTaskWithTitle() {
  439.         ...
  440.  
  441.         List<Task> expectedTasks = page.joinTasks(defaultTasks, newTask);
  442.         page.assertEqualListsOfTasks(expectedTasks, page.availableTasks());
  443.         /*
  444.             вот тут нам реализация assertTasks(Task... expectedTasks)
  445.             особенно пригодится
  446.  
  447.             т к меньше потребуется телодвижений
  448.  
  449.             получишь
  450.             TasksApi.assertTasks(DEFAULT_TASKS[0], DEFAULT_TASKS[1], new Task("give lesson", "", false, TasksApi.uri + "/3"));
  451.         */
  452.     }
  453. ***********************************************************************
  454.     @Test
  455.     public void testUpdateTaskTitle() {
  456.         page.resetTasks();
  457.  
  458.         Task newTask = new Task(Helpers.getUniqueText("t "));
  459.         /*
  460.             большого смысла нету использовать getUniqueText
  461.             на начало теста - известно  - какие таски есть
  462.  
  463.             нету никаких предпосылок использовать уникальный текст
  464.         */
  465. ********************************
  466.  
  467.     @Test
  468.     public void testCreateUpdateDelete() {
  469.         page.resetTasks();
  470.         Task testTask = new Task("give lesson");
  471.  
  472.         Response response = page.sendAuthorizedPostRequest(URI, testTask);
  473.         //page.assertStatus(201, response);
  474.         //page.assertEqualListsOfTasks(page.joinTasks(defaultTasks, testTask), page.availableTasks());
  475.  
  476.         String addedTaskUri = page.taskFromResponse(response).getUri();
  477.         testTask.setTitle("stop lesson");
  478.         response = page.sendAuthorizedPutRequest(addedTaskUri, testTask);
  479.         //page.assertStatus(200, response);
  480.         //page.assertEqualListsOfTasks(page.joinTasks(defaultTasks, testTask), page.availableTasks());
  481.  
  482.         page.sendAuthorizedDeleteRequest(addedTaskUri);
  483.         page.assertEqualListsOfTasks(defaultTasks, page.availableTasks());
  484.     }
  485. /*
  486.     сложновато как-то код читается)
  487.  
  488.     хорошо бы после каждого действия - проверить состояние тасок
  489.     можно получить что-то такое
  490.  
  491. */
  492.     @Test
  493.     public void testCreateUpdateDelete() {
  494.  
  495.         Task task = new Task("t title", "t description", false, TasksApi.uri + "/3");
  496.  
  497.         TasksApi.create(task);
  498.         TasksApi.assertTasks(DEFAULT_TASKS[0], DEFAULT_TASKS[1], task);
  499.  
  500.         Task editedTask = new Task ("t title edited", "t description edited", true, TasksApi.uri + "/3");
  501.  
  502.         TasksApi.update(editedTask);
  503.         TasksApi.assertTasks(DEFAULT_TASKS[0], DEFAULT_TASKS[1], editedTask);
  504.  
  505.         TasksApi.deleteById(3);
  506.         TasksApi.assertTasks(DEFAULT_TASKS[0], DEFAULT_TASKS[1]);
  507.  
  508.     }
  509. *****************************************
  510.     static class ErrorContainer {
  511.  /*
  512.      А эти классы как раз стоит разместить в пакете containers -
  513.      т к по сути это и есть контейнеры для парсинга с JSON
  514.      касается и других классов
  515.  */
  516. ****************************************************
  517. Строки подсвечены красным в идее, при этом проект компилируется и раннится, в чем проблема?
  518. import java.util.Base64;
  519. return requestBuilder.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("miguel:python".getBytes()));
  520. /*
  521.     у меня все ок с твоим же проектом
  522.     http://joxi.ru/vAW36KgskJl5oA
  523.  
  524.     может быть - что-то связано с версиями джавы
  525.     я сначала делала на джаве помладше это задание
  526.     см на код и на закомменченный код
  527. */
  528. //import org.jboss.resteasy.util.Base64; //for java7 & up
  529. import java.util.Base64;//since java 1.8
  530.  
  531. public class RestHelpers {
  532.  
  533.     public static Invocation.Builder requestTo(String uri) {
  534.         return ClientBuilder.newClient().target(uri).request();
  535.     }
  536.  
  537.     public static Invocation.Builder authorized(String credentials, Invocation.Builder requestBuilder) {
  538.         //return requestBuilder.header("Authorization", "Basic " + Base64.encodeBytes(credentials.getBytes()));//for java7 & up
  539.         return requestBuilder.header("Authorization", "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes()));
  540.     }
  541. }
  542. ******************************************************************************
  543. надо ли, реализуя паттерн page object, называть тут объекты не пейджами, а ресурсами:
  544. TasksResource resource = new TasksResource()
  545. /*
  546.     это я выше ответила
  547.     да, надо
  548.     смысла реализовывать ресурс-объект - особо нет
  549.     можно реализовать ресурс-модуль
  550.  
  551.     это так, лирическое отступление
  552.     На реальном проекте обычно создают класс-ресурс (объект или модуль) не сразу, когда начали писать тесты.
  553.     А позже - когда появится необходимость переиспользовать вспомогательные методы в разных тест-классах.
  554.     Или если одни более квалифицированные члены команды разрабатывают класс-ресурс,
  555.     а другие - используют для разработки тестов. Но или если так принято в данной команде)
  556.     Т е - это делается обычно при необходимости. С пейджами - в общем аналогично.
  557.  
  558. */
  559. ************************************************
  560. Когда я проверяю результаты выполненного запроса, я использую свой метод assertTaskListEqual(),
  561. но у меня еще есть отдельная проверка статуса респонса.
  562. Нужна ли она вообще? Если да, хорошо бы ее запихнуть в тот же метод assertTaskListEqual(),
  563. но как лучше - передавать параметром в метод - вроде как усложнять его?
  564. Написать для проверки статуса отдельный метод? Но тогда проверка результатов будет в две строчки...
  565. Сделала пока так: page.assertStatus(200, response);
  566. но очень хочется, чтобы была запись типа response.assertStatus(200); или просто assertStatus(200);...
  567. /*
  568.     ага, и эту мысль ты учуяла)
  569.  
  570.     да
  571.     проще позапихивать проверку кода ответа  в сами методы-действия
  572.     про недостатки такого решения было выше
  573.  
  574.     в общем-то - оправданая мера
  575.     и методов лишних не нужно будет)
  576. */
  577. ***
  578. В целях улучшения кода я бы назвала assertEqualListOfTasks(expectedTasks, actualTasks)
  579. вот так: assertList(), но тогда не совсем в паре выглядит метод assertEqualTasks
  580. (его б тогда назвать assertEqual)
  581. Или назвать их просто assertListOfTasks, assertTasks, или даже assertTasks и assertTask?
  582. /*
  583.     у тебя будет assertTasks
  584.  
  585.     а для проверки одной таски - я бы использовала стандартный junit-овский assertEqual
  586.     он ничем не хуже - просто, коротко и наглядно
  587.     не вижу смысла это заворачивать
  588.  
  589.     писала выше - еще не факт - что вообще это стоит проверять...
  590.     вопрос дискуссионный, конечно - от многого зависит
  591.     как компромисс - такое проверять в фиче-тестах
  592.     а в е2е - уже е проверять
  593.     (тут - только состояние всего списа тасок - как более показательную проверку)
  594. */
  595. ***************************************************************
  596. Я сделала отдельные классы Task, TaskContainer, TasksContainer, может надо их не выносить в отдельные, а оставить внутри класса TaskPage, как было сделано с классом Task в ToDoMVCPage?
  597. /*
  598.     выше написала - в один пекедж и отдельно от тест-класса
  599.     не надо его загромождать)
  600.     http://joxi.ru/EA4k7zEUDgM1z2
  601.     что-то такое получишь)
  602. */
  603. *********************************************************************
  604. Для облешчения читабельности кода самих тестов можно завернуть получение респонса в отдельный метод типа
  605. Response sendGetRequest(String uri)
  606. Response sendPostReauest(String uri, Task task)
  607. Response sendPutRequest(String uri, Task task)
  608.  
  609. сделала так, не лишнее?
  610. /*
  611.     написала про это)
  612.     думаю - лишнее)
  613. */
  614. ***
  615. sendAuthorizedGetRequest используется у меня чаще, намного,
  616. чем просто sendGetRequest, может переименовать их на sendGetRequest и sendUnauthorizedGetRequest?
  617. аналогично с post, put, delete. Будет меньше букв, но какб название метода не будет точно говорить,
  618. что он делает... Или будет?)
  619. /*
  620.     да, на уровне класса-ресурса - можно имена методам дать полаконичнее
  621.     часть методов - переселить)
  622.     часть методов - убить)
  623.  
  624.     и получится что-то типа такого
  625. */
  626. public class TasksApi {
  627.  
  628.     public static String uri = Configuration.baseUri + "/todo/api/v1.0/tasks";
  629.  
  630.     public static Invocation.Builder authorizedRequest(String uri)
  631.  
  632.     public static void reset()
  633.  
  634.     public static List<Task> get()
  635.  
  636.     public static Task create(Task task)
  637.     public static Task create(String taskTitle)
  638.  
  639.     public static Task update(Task task)
  640.  
  641.     public static void deleteById(int taskId)
  642.  
  643.     public static void assertTasks(Task... expectedTasks)
  644. }
  645. /*
  646.     т е - оперируем - простыми лаконичными словами )
  647. */
  648. ***
  649. А может надо от методов типа sendAuthorizedPostRequest вообще перейт к методам addTask/EditTask/deleteTask,
  650. тогда вообще все красиво, но мы ведь вроде АПИ тестируем, т.е. запросы тестируем, а не UI... Как тут правильнее?
  651. /*
  652.     ага, только рассказала про это )
  653.     то, что мы тестируем запросы - опредетяется еще на уровне самого теста
  654.     можно проект как-то назвать - что это API Tests
  655.     если в одном проекте тесты и для Web UI, и для API Tests
  656.     то я бы на уровне пекеджей это разграничила
  657.  
  658.     да и имя тест-метода - тоже говорит нам про то - что и как мы тестим
  659.  
  660.     сильно дорого - каждый метод вот так сложно называть )
  661.     потом смотришь на код - и приходится долго думать и вчитываться
  662. */
  663. ****
  664. появилась проблемка с uri:
  665. public void setUri(String uri) {
  666.         uri = uri;
  667.     } - почему-то сеттер был написан так, а не this.uri = uri,
  668.  
  669. когда я его поменяла, то мой метод assertEqualTasks стал падать,
  670. потому что uri of actual task = реальному uri c индексом вконце,
  671. а uri of expected task = null......
  672. (ну да, мы же при создании объекта типа Task, uri никак не записываем,
  673. потому что мы не знаем, какой он будет, сколько там в базе реально уже тасков...)
  674. пока в ассерт-методе решила не сравнивать uri.
  675. /*
  676.     в сеттере правильно - this.uri = uri
  677.  
  678.     нулла не будет - если будешь uri для таски прописывать
  679.     на самом деле - все мы знаем)
  680.     мы же сами ресет делали)
  681.     и сами что-то делали после ресета)
  682.     и знаем какой uri - т к видели как работает метод create )
  683.     для нас это нормальная задача - благодаря ресету
  684. */
  685. ***************************************************************************************
  686.  
  687.  
  688. задумалась, как все-таки удобнее, я сделала ресет списка тасков до двух изначальных тасков.
  689. При таком способе сложнова-то проверять каждый раз, какие таски есть, зато для некоторых тестов не надо
  690. дополнительных приготовлений (для теста удаления, редактирования, например)...
  691. /*
  692.  
  693.     логичные размышления)
  694.  
  695.     был у нас уже такой диалог - что лучше
  696.  
  697.     как реализовать предварительые действия - delete all vs reset
  698.  
  699.     когда предварительные действия в тест-метода =  delete all + add some tasks
  700.  
  701.     и вариант reset = когда при его вызове - снова получаем те же 2 таски, что были на начало теста...
  702.  
  703.     Delete all
  704.     В такой реализации - код удаления всех тасок без каких-то сложностей особенных.
  705.     Это плюс. А минус в том, что для гивен-методов тогда надо реализовывать дополнительное что-то.
  706.     На начало тест-метода часто нужны существующие таски - нам же надо реализовать тесты для апдейта или удаления.
  707.  
  708.     Судя по реализации гивенов - когда в гивенах создаются таски -  то используется сначала delete all,
  709.     а потом добавление тасок.
  710.     Т е действий - больше, чем если бы мы реализовали reset.
  711.  
  712.     Если deleteAll or  add отвалились - то гивены не работают.
  713.     Хотя, с другой стороны, если reset отвалился - тоже беда)))
  714.  
  715.     Но, в таком случае, все же это меньшее зло,
  716.     т к этот reset служит только для тестовых целей
  717.     и с развитием сервера он вряд ли будет меняться,
  718.     да и логика у него и у так реализованных гивенов - значительно проще...
  719.  
  720.     Более того, с reset получше вероятности по надежности гивенов -
  721.     с reset только одно звено (примитивное),
  722.     а с delete all - два (реально использующихся в рабочих целях = более сложных = add + delete all)
  723.  
  724.     Еще такой момент.
  725.     При тестировании операции add - вызывать гивен, в котором уже вызван add - как-то странно)
  726.     Т е при таких раскладах - надо вызывать гивен строго в ситуации, кгда тасок мы не добавляем
  727.     Это конечно не KISS тоже - то, что надо помнить о каких-то ограничениях
  728.  
  729.     Из этой же серии
  730.     delete all - тоже операция, которая может быть востребованой в работе приложения
  731.     Значит - и ее надо тестировать)
  732.     А раз так - для delete all вызываем гивен, в котором вызываем delete all
  733.     Тут уже идея с delete all в качестве вспомогательной операции - заходит в тупик)
  734.  
  735.     С Delete all хорошо то, что гивены гибкие и можно более интересную тестовую ситуацию создать.
  736.     правда, вопрос - кому она тут нужна)))
  737.     т к тут, на этом этапе - достаточно простые вещи нам нужно проверить
  738.     супер-навороченых гивенов в общем-то и не надо
  739.  
  740.  
  741.     вывод -  =  с reset = 2 таски по дефолту более KISS получается)
  742.     если бы стояла задача в REST API тестах прогнать работу с тасками  -
  743.     у которых тексты разной длины/кодировки ....,
  744.     то может - навороченные гивены, использующие delete all, были бы и нужны.
  745.     да и то - не факт )
  746.     И вот это очень серьезное замечание - когда delete all тестируется используя в предварительніх действиях саму себя
  747.     Я - сторонник варианта с reset - как варианта, в котором меньшее количество вещей может сломаться
  748.     и как более быстрого и более однозначного варианта
  749.  
  750. */
  751.  
  752. ***
  753. При отладке заметила следуюзую проблему:
  754. тест testUpdateTaskTitle падал (респонс возвращал статус 400 Bad Request).
  755. При этом аналогичный тест testUpdateFullInformation успешно проходил.
  756. Проблема была в содержимом передаваемого таска: в первом случае инициализировано было только поле title,
  757. description был null, а во втором и title, и description были заполненые нормальными стрингами.
  758. Тогда я в конструкторе Task(String title) дописала строку, чтобы полю description присваивалась пустая строка "".
  759. После этого тест стал проходить. Правильно ли я нашла и пофиксила проблему?
  760. /*
  761.     да, правильно )
  762.  
  763.     я бы только для создания таски проверяла вариант с созданием по имени и по полной информации
  764.  
  765.     для апдейта - реализовывала бы один тест
  766.     для таски
  767.     мы же уже оперируем таской - мы ее собираемся редактировать
  768.     можем ей все наменять - кроме uri
  769.  
  770.     вот и наменяй)
  771. */
  772. *******************************************************
  773. Надо ли писать дополнительные тесты создания таска не с json форматироваием, не с юникодом в тайтл-дескрипшен,
  774. с пустым тайтлом? вроде бы нет, т.к. это будет уже слишком много тестов,
  775. вроде как такое должно покрываться юнит-тестами
  776. /*
  777.     да, это уже дело юнит-тестов
  778.     ты права)
  779. */
  780. ************************************************************
  781. Нужен ли нам тест GET запроса для сервиса .../tasks/<id> - получение одного конкретного таска
  782. /*
  783.     резонный вопрос)
  784.     да, можно и это покрыть)
  785. */
  786. *************************************************
  787. В конце демо-лекци Яков произнес что-то про плагины для тестирования API?
  788. Надо попробовать все это еще в каком-то плагине?
  789. ЛИи я что-то не так поняла? Речь про restclient-idea-plugin?
  790. /*
  791.     это про то - что не только с командной строки с curl можно работать
  792.  
  793.     погугли что-то типа chrome plugins curl
  794.     речь про  - как с curl можно работать легче и проще
  795. */
  796. *******
  797. Глобальный вопрос:
  798. В статье https://www.javacodegeeks.com/2011/10/java-restful-api-integration-testing.html описано,
  799. что именно надо тестировать в RESTful ресурсе:
  800.  
  801. И сказано, что каждый тест должен содержать только один ассерт, т.е.
  802. Код ответа, заголовки и payload - это три отдельных теста.
  803. А у меня пока это все одн тест (и заголовки не тестируются никак).
  804. Дальше там вот что написано:
  805.  
  806. This is a rather simple test,
  807. which verifies that a basic happy path is working,
  808. without adding to much complexity to the test suite.
  809. If, for whatever reason it fails, then there is no need to look at any other test for this URL until this is fixed.,
  810. логично, конечно же. МОжет и нам надо так сделать? :-)
  811.  
  812. /*
  813.     интересная статья)
  814.  
  815.     автор кстати и сам пишет
  816.     Focusing on a clear separation always has benefits,
  817.     but when doing this kind of black box testing it’s even more important,
  818.     as the general tendency is to write complex test scenarios in the very beginning. (я про эту строчку)
  819.  
  820.     в принципе - мы из этих же соображений писали мало е2е и много фиче-тестов - чтоб они были независимыми
  821.     и мы получили максимально полный фидбек от тестов - вне зависимости от того, что не работает
  822.  
  823.     нам - точно не надо так делать)
  824.     если тебе понадобится так сделать - все знания для этого у тебя уже есть
  825.  
  826.     тут - достаточно тонкая грань)
  827.  
  828.     когда мы детализируемся слишком, даже если все структурно продумано
  829.     количество сопровождаемых тестов - будет сильно расти
  830.  
  831.     надо каждый раз думать про цену)
  832.  
  833.     например, в таком-то запросе - нас не устроил код ответа
  834.     вот исправили ошибку
  835.     и логично было бы - проверить не только код ответа)
  836.     но и что вернул запрос)
  837.     а также - каково истинное положение дел после выполнения запроса
  838.  
  839.     а теперь представь - что на это у тебя несколько тестов)
  840.     и тебе надо сообразить - какие тесты надо проверить
  841.  
  842.     а т к этих тестов - вообще очень много - придется покопаться)
  843.  
  844.     допускаю - что существуют такие ситуации - когда стратегия, описанная в тратье - будет как раз
  845.     то, что надо
  846.  
  847.     надо каждый раз примерять это на цели и задачи - а стоит ли, а оправдано ли
  848.  
  849.     Якова еще поспрашиваю про это)
  850.     хороший вопрос задала)
  851. */
  852.  
  853. ******
  854. Еще нашла симпатичную вещь вот тут
  855. https://blog.philipphauer.de/testing-restful-services-java-best-practices/ :
  856. IntelliJ IDEA generates those setters for you.
  857. Moreover, there is a nice shortcut.
  858. Just write the fields (likeprivate String name;) and hit Alt+Insert , Arrow-Down
  859. until “Getter and Setter”, Enter ,
  860. Select “Builder” for “Setter template”, then Shift+Arrow-Down  (multiple times) and finally press Enter.
  861. This is so cool!
  862. Afterwards you can use the fluent setters to write readable and typesafe code for creating test data.
  863. 1
  864. 2
  865. 3
  866. 4
  867. BlogDTO newBlog = new BlogDTO()
  868.         .setName("Example")
  869.         .setDescription("Example")
  870.         .setUrl("www.blogdomain.de");
  871.  
  872.  
  873.  
  874. Я сейчас использую два разных конструктора, но может так будет красивее и понятнее сам код теста?
  875. /*
  876.     ну, для вот таких несложных случаев - я бы не применяла Builder Pattern )
  877.     да, код красивее и понятнее, это да)
  878.  
  879.     можешь просто в рамках изучения паттерна попробовать это реализовать)
  880.     в конце ревью приведу тебе полезное на почитать
  881.  
  882.     этот Builder Pattern - можно применять безотносительно того, что ты реализуешь
  883.     в случаях - когда тебе надо что-то многокомпонентно и вариантивно собрать -
  884.     будет хорошим решением
  885.  
  886. */
  887.  
  888. ******
  889. Еще отсюда: получается, аккуратнее и понятнее код был бы, если бы я в тесте оперировала бы не Response response
  890. переменной, а сразу уже десериализованным объектом типа Task.
  891. Но тогда будет не оч удобно проверять статус респонса….
  892. По последней ссылке используется какая-то дополнительная библиотека
  893. /*
  894.     да, есть на нее линка в тексте
  895.    
  896.     хочешь - поразбирайся
  897.    
  898.     код и правда красивый
  899.    
  900.     как реализовать то, что ты описала в вопросе - я в ревью уже ответила )
  901.    
  902.     молодец, много в вопросах такого спросила)
  903. */
  904. *******************************************
  905. /*
  906.  
  907.    Про Builder Pattern - на простых примерах)
  908.  
  909.    https://jlordiales.me/2012/12/13/the-builder-pattern-in-practice/
  910.    http://www.journaldev.com/1425/builder-design-pattern-in-java
  911.  
  912.    По этим линкам - по моему мнению - неплохо и несложно разъяснено про паттерн Builder
  913.    по второй линке - вообще пошагово
  914.  
  915.    еще и это пригодится
  916.     вот неплохие линки - про использование this
  917.            https://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html
  918.            http://stackoverflow.com/questions/2411270/when-should-i-use-this-in-a-class
  919.  
  920.    буду иносказательной и я)
  921.  
  922.    У нас будет класс = то, что мы строим
  923.    и его внутренний класс (а если точнее - nested static class) = builder = как мы это строим
  924.  
  925.    мы хотим строить пошагово - сначала рассказать в сколь угодно много шагов(любая их комбинация)
  926.    а потом - по собранной в шагах информации - построим наш объект
  927.  
  928.    и поскольку - чтобы строить объект - мы применяем класс-строитель
  929.    то - конструктор для нашего объекта - не нужно делать public
  930.  
  931.    Допустим, мы делаем некий напиток
  932.    мы можем добавлять
  933.        воду
  934.        сахар
  935.        красители
  936.        алкоголь
  937.        фрукты
  938.    паковать это в некую тару
  939.  
  940.    ну и вот это все в комплексе - и есть создание напитка
  941.  
  942.    вот и создаем класс Drink = т к делаем мы напиток
  943.    и у него внутри - реализуем и класс-строитель = Barman
  944.  
  945.    public class Drink {
  946.        private int water;
  947.        private int sugar;
  948.        private List<Colour> colours;
  949.        private List<Alcohol> alcohols;
  950.        private List<Fruit> fruits;
  951.        private Bottle bottle;
  952.        /*
  953.            все свойства конечного объекта - описываем как private поля объекта
  954.        */
  955.  
  956.        private Drink(Barman barman) {
  957.            water = barman.water;
  958.            sugar = barman.sugar ;
  959.            colours = barman.colours;
  960.            alcohols = barman.alcohols;
  961.            fruits = barman.fruits;
  962.            bottle = barman.bottle;
  963.            ...
  964.        }
  965.        /*
  966.            конструктор этого класса = тоже private
  967.            и в качестве параметра он получает как раз объекта-строителя
  968.  
  969.            в конструкторе - переносим данные из строителя  - в поля нашего объекта
  970.            и далее - (см "..." в коде) при необходимости как-то работаем с данными
  971.        */
  972.  
  973.  
  974.        public static class Barman {
  975.            private int water;
  976.            private int sugar;
  977.            private List<Colour> colours;
  978.            private List<Alcohol> alcohols;
  979.            private List<Fruit> fruits;
  980.            private Bottle bottle;
  981.            /*
  982.                у класса-строителя - такой же набор полей
  983.                и они тоже private
  984.  
  985.                все методы этого класса, кроме одного(build()) -
  986.                как раз и будут собирать эти свойства
  987.            */
  988.  
  989.            public Barman(Bottle bottle) {
  990.                this.bottle = bottle;
  991.                colours = new ArrayList<Colours> ();
  992.                ...
  993.            }
  994.            /*
  995.                конструктор класса-строителя - объявляем как public
  996.                здесь мы инициализируем все поля этого класса
  997.  
  998.                если это необходимо - можно реализовать конструктор с параметрами
  999.                цель - передать то, что будет обязательным для заполнения
  1000.            */
  1001.  
  1002.            public Barman addWater(int water) {
  1003.                this.water += water;
  1004.                return this;
  1005.            }
  1006.            /*
  1007.                каждый из методов, заполняющих какое-либо из свойств - это public метод
  1008.                и возвращает такой метод самого объекта-строителя
  1009.            */
  1010.  
  1011.            public Barman addFruits(Fruit... fruits) {
  1012.                for (Fruit fruit:fruits) {
  1013.                    this.fruits.add(fruit);
  1014.                }
  1015.                return this
  1016.            }
  1017.            /*
  1018.                заметь - такие методы не заново заполняют соответствующие поля - а лишь дополняют то,
  1019.                что мы изначально задали в конструкторе
  1020.  
  1021.                цель - иметь возможность многократно и в любой последовательности вызывать методы строителя
  1022.  
  1023.                для каждого из свойств - реализуем такого плана метод
  1024.            */
  1025.  
  1026.            public Drink build() {
  1027.                return new Drink(this);
  1028.            }
  1029.            /*
  1030.                а этот метод класса-строителя - возвращает новый объект - который мы как раз строим
  1031.                и передаем в конструктор - this = наш объект-строитель
  1032.  
  1033.                собственно - фактически это и будет создание нашего объекта Drink
  1034.                (вспомни - что мы делали в конструкторе класса Drink)
  1035.            */
  1036.        }
  1037.    }
  1038.  
  1039.    /*
  1040.        если минимально - то это все)
  1041.  
  1042.        теперь мы можем создать нашего строителя
  1043.        new Drink.Barman(bottle)
  1044.  
  1045.        а чтоб эту часть кода сделать нагляднее - можем в классе Drink
  1046.        реализовать static метод, возвращающий новый объект типа строитель
  1047.  
  1048.        например
  1049.  
  1050.    */
  1051.    public class Drink {
  1052.        ....
  1053.  
  1054.        public static Barman composeDrink(Bottle bottle) {
  1055.            return new Barman(bottle);
  1056.        }
  1057.  
  1058.        public static class Barman {
  1059.            ...
  1060.        }
  1061.    }
  1062.  
  1063.    /*
  1064.         и далее в коде
  1065.         применив import static для  composeDrink -
  1066.         писать
  1067.    */
  1068.  
  1069.        composeDrink(bottle)
  1070.  
  1071.        а не
  1072.  
  1073.        new Drink.Barman(bottle)
  1074.  
  1075.    /*
  1076.        ну и далее - начинается самое интересное
  1077.  
  1078.        composeDrink(bottle) - возвращает объект-строитель
  1079.        у которого много методов, "доливающих" наш напиток = уточняющих наш рецепт
  1080.  
  1081.        а т к каждый из этих методов - возвращает тот же объект
  1082.        мы можем строить любые цепочки вызовов
  1083.        и заканчивать такую цепочку - вызовом build
  1084.  
  1085.        только в вызове build -  и будет построен объект
  1086.        согласно тому - что мы уточнили ранее
  1087.  
  1088.    */
  1089.       composeDrink(bottle).addWater(...).addAlcohol(...).addFruits(...).addColor(...).addFruits(...).build(); - некий коктейль )
  1090.       ...
  1091.       composeDrink(bottle).addWater(...).build(); - просто бутылка с водой
  1092.       ...
  1093.       composeDrink(bottle).build();  - вообще пустая бутылка )
Add Comment
Please, Sign In to add comment