julia_v_iluhina

Untitled

Nov 25th, 2016
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 18.37 KB | None | 0 0
  1. http://joxi.ru/EA4k7zEUDdq172
  2.  
  3. /*
  4.     1 и 2 - одинаковые куски кода
  5.  
  6.     ты уже используешь
  7.      tasks = deepcopy(default_tasks)
  8.  
  9.     так что - и вначале это вполне уместно было бы)
  10. */
  11. ************************
  12. @app.route('/todo/api/v1.0/tasks/default', methods = ['GET'])
  13. @auth.login_required
  14. def get_default_tasks():
  15. /*
  16.     согласна - GET-запрос - тут уместнее
  17.     реализация - тоже ок
  18.     я бы сэекономила на последней строчке)
  19.     реализовала как return get_tasks()
  20.     выигрыш на самом деле небольшой
  21.     но на мой вкус так проще) и чуть более DRY)
  22.  
  23.     еще - я бы применяла uri = /todo/api/v1.0/tasks/reset
  24.     ну и имя метода из этих же соображений - reset_tasks()
  25.  
  26.     ресет - точнее отвечает тому что мы делаем
  27.     мы не просто отдаем таски по умолчанию
  28.     мы нашу базу приводим к начальному состоянию
  29.  
  30.     про то - почему тут Get уместнее
  31.  
  32.         Нам нужно реализовать в rest-server.py возможность для очистки базы
  33.  
  34.         У нас же REST сервис, потому нужно следовать “идеологии REST”
  35.  
  36.         А она говорит - что можно использовать только специальные http methods
  37.         И только по определенному принципу
  38.         Например,
  39.         для создания - использовать post,
  40.         для апдейта - метод put
  41.         и т д
  42.  
  43.         И нам важно соблюсти этот принцип и свести написание сервисов к одному шаблону
  44.         Вся идея REST была в том, чтобы чтобы потом все могли “общаться на одном языке” и таким образом “понимать друг друга”
  45.  
  46.         можно было бы реализовать что-то типа такого
  47.         @app.route('/todo/api/v1.0/tasks/reset', methods = [‘PUT’])
  48.  
  49.         но, хотя мы и делаем “апдейт"
  50.         но - апдейт не ресурса...
  51.         т к reset - это не ресурс
  52.  
  53.         вот если бы мы для  /tasks посылали PUT, то было бы все честно
  54.         а /tasks/reset - это уже не ресурс
  55.         это просто “хак-урл” , чтобы нам выполнить определенное нужное нам действие
  56.         потому в данном случае абсолютно нормально - посылать GET
  57.         наверное, можно и PUT… решай самостоятельно
  58.  
  59.         еще довод за GET
  60.         обрати внимание - все остальные запросы что-то возвращают - делают return …
  61.         это логично - клиент послал запрос, нужно ему что-то вернуть)
  62.         и тут было бы логично вернуть все таски = (то же, что возвращает запрос get)
  63.         это важный довод - почему стоит использовать именно GET для нашего запроса
  64.  
  65. */
  66. ***************************************
  67. http://joxi.ru/8An6LoZhqBxJ0A
  68.  
  69. /*
  70.     Вот еще источник возможных проблем
  71.     если добавлять первую таску - то будут проблемы
  72.  
  73.     https://lancelote.gitbooks.io/intermediate-python/content/book/ternary_operators.html
  74.     https://lancelote.gitbooks.io/intermediate-python/content/book/comprehensions.html (dict абстракции)
  75.     http://www.diveintopython.net/native_data_types/index.html#odbchelper.dict
  76.  
  77. */
  78. ************************************
  79. public class RESTfullTextsTest {
  80. /*
  81.     чуть подправить - чтоб по конвеншенсам - CamelCase
  82.     https://google.github.io/styleguide/javaguide.html#s5.3-camel-case
  83.  
  84.     и термин = RESTful API
  85.     одна буковка у тебя лишняя)
  86.  
  87.     получим - RestFulTasksTest
  88.  
  89. */
  90. *********************************
  91. src / main / java / com / tasj / entities /
  92.  
  93. /*
  94.     я бы использовала термин containers для этого пекеджа
  95.     и класс ErrorContainer - тоже сюда переноси
  96.  
  97.     того же плана класс
  98.  
  99.     кстати, никаким классам-контейнерам, кроме класса Task - и не надо реализовывать
  100.     ни конструкторов, ни equals, ни hashCode
  101.  
  102.     может - equals и hashCode - для ErrorContainer
  103.     и то, как по мне - не очень нужно
  104.  
  105.     кстати - для классов тех объектов, которые будешь сравнивать
  106.     реализуй toString - чтоб в случае чего - сообщения об ошибке были наглядными
  107. */
  108. *****************************
  109.  public static final String URI = "http://localhost:5000/todo/api/v1.0/tasks";
  110.  
  111. /*
  112.     Тут есть решение более настраиваемое
  113.  
  114.     final - нам не очень подходит
  115.  
  116.     вот эта часть - http://localhost:5000/ - зависит от того - где наш рест-сервер задеплоен
  117.     остальная часть - это уже скойство нашего рест сервера
  118.  
  119.     вот в Сonfiguration.baseUri  - сохраним http://localhost:5000 = домен
  120.     чтобы его можно было от случая к случаю реконфигурить...
  121.     Ведь это как раз та часть урла - которая не принадлежит ресурсу,
  122.     и зависит от того на каком сервере/по-какому-адресу ресурс/веб-сервис задеплоен
  123.  
  124.     На самом деле здесь есть несколько способов как лучше сделать
  125.  
  126.     Первый, если ты уверена, что тебе не нужно будет одновременно/параллельно посылать запросы на разные урлы
  127.     То тогда можно использовать Configuration.baseUrl типа обычная глобальная переменная
  128.  
  129.     если нет, тогда лучше "ооп" подход, когда обьект-ресурс помнит свой базовый урл (доменную часть)
  130.     который передается при создании через конструктор
  131.  
  132.     нам модульный тут вполне подходит
  133.  
  134.     а полный uri - уже собирай внутри класса-ресурса
  135.  
  136. */
  137. ****************************
  138.   public static Invocation.Builder authorized(Invocation.Builder request){
  139.         return request.header("Authorization", "Basic " + DatatypeConverter.printBase64Binary("miguel:python".getBytes()));
  140.   }
  141. /*
  142.     не зашивай внутрь метода вот это "miguel:python"
  143.     передавай как параметр эту строчку
  144.  
  145.     так метод будет абсолютно универсальным
  146.  
  147.     а уже внутри класса-ресурса - реализуешь свой authorizedRequest(String uri)
  148.     в котором вызовешь Connections#authorized
  149.     и передашь туда credentials = "miguel:python"
  150. */
  151. ********************************
  152. public class Connections {
  153.  
  154. /*
  155.     подумай над названием RestHelpers  )
  156.     потенциально - может немного разрастись)
  157.  
  158.     не ограничивай себя именем класса раньше времени)
  159. */
  160. ************************
  161. public class RestResources {
  162.  
  163. /*
  164.     Это наш класс-ресурс
  165.     По аналогии с пейджами, класс-ресурс выносим  в пекедж  - resources.
  166.  
  167.     Поскольку мы работаем только с тасками - в имена методов-действий можно не включать слово Tasks
  168.     Имена методов-проверок - по-прежнему указываем точно
  169.  
  170.     А этот класс - можно было бы назвать Tasks ( т к ресурс-модуль, а не ресурс-объект = TasksResource)
  171.     И в коде потом можно без статического импорта писать
  172.             Response response = Tasks.create(URI, "give lesson");
  173.  
  174.     Это было бы и наглядно, и на будущее - на случай, если в тесте появятся запросы к другим ресурсам
  175.  
  176.     Варианты имени для класса-ресурса
  177.     Tasks - неплохое имя
  178.     но у нас есть класс Task
  179.     и можно сказать - что мі используем один термин для достаточно разных понятий….
  180.  
  181.     Можно в таком случае для класса-ресурса использовать TasksApi или  TasksRest в таком случае…
  182.  
  183.     Но и Tasks - тоже неплохо…, но хуже)
  184.     Можно пойти на эту уступку в принципе - код понятный, названия наглядные
  185.  
  186.     На реальном проекте обычно создают класс-ресурс (объект или модуль) не сразу, когда начали писать тесты.
  187.     А позже - когда появится необходимость переиспользовать вспомогательные методы в разных тест-классах.
  188.     Или если одни более квалифицированные члены команды разрабатывают класс-ресурс, а другие - используют для разработки тестов.
  189.     Но или если так принято в данной команде)
  190.     Т е - это делается обычно при необходимости. С пейджами - в общем аналогично.
  191.  
  192. */
  193. **************************************
  194.    public static Response getRequest(){
  195.    public static Response getDefaultRequest(){
  196.    public static Response putRequest(int taskId, Task task){
  197.    public static Response postRequest(Task task){
  198.    public static Response deleteRequest(int taskId){
  199. /*
  200.     можно лаконичнее называть методы
  201.  
  202.     get
  203.     reset
  204.     create / post
  205.     update / post
  206.     delete
  207.  
  208.  
  209.     Правильный статус ответа на каждый из запросов - строго определенный
  210.         И если мы будем проверять статут ответа в тест-методе - то надо об этом думать и вспоминать - какой статус в каких случаях верный
  211.         И можно выкрутиться - проверять статус ответа в нутри степа, а возвращать собственно entity из запроса.
  212.         Сразу пара потенциально полезных моментов)
  213.  
  214.     Если встроить проверку response кода прямо в сам степ
  215.     Тут сразу появляется интересный нюанс
  216.     Дело в том, что мы так спрячем тестовую логику внутрь степа, что не очень хорошо
  217.     С другой стороны - эта проверка = само собой разумеющееся
  218.     Эта проверка достаточно стандартная и всегда будет повторяться после вызова степа (если будет жить вне степа)
  219.     А раз так - то почему бы и не спрятать?
  220.     На самом деле - нет однозначного ответа на этот вопрос...
  221.     Так - со спрятанной проверкой - код станет проще во всех тестах - но станет до некоторой степени “магическим”
  222.     Тут надо решать самостоятельно - как лучше )
  223.     На твое усмотрение
  224.     Еще момент
  225.     В этих API тестах - самих тестов может быть не так уж и много -
  226.     чтоб еще и заморачиваться над DRY кода...нужно и этот аспект учитывать
  227.  
  228.  
  229.     Но если пойти этим путем = прятать в степе проверку статуса response, то код будет намного более удобным:
  230.         @Test
  231.         public void testCreate() {
  232.             Response response = Tasks.create("give lesson");
  233.  
  234.             assertEquals(201, response.getStatus());
  235.             assertEquals("give lesson", titleFor(response));
  236.         }
  237.  
  238.     vs
  239.  
  240.         @Test
  241.         public void testCreate() {
  242.             Task task = Tasks.create("give lesson");
  243.  
  244.             assertEquals("give lesson", task.getTitle());
  245.         }
  246. */
  247. *********************************
  248.     @Test
  249.     public void testCreate() {
  250.         Response response = postRequest(new Task("task title"));
  251.  
  252.         assertEquals(201, response.getStatus());
  253.         assertEquals("task title", getTaskFrom(response).getTitle());
  254.         /*
  255.             если и проверять таску из ответа - то уже используй
  256.             assertEquals(new Task(....), getTaskFrom(response))
  257.  
  258.             хотя - решение - проверять таску из ответа или нет - надо отдельно проверять
  259.  
  260.             если это уже на каком-то более низком уровне проверяется
  261.             то тут - только проверили статус ответа
  262.             и важное = состояние списка тасок (всего списка) = функциональную проверку делаем
  263.         */
  264.  
  265.         List<Task> receivedTasks = getTasksFrom(getRequest());
  266.  
  267.         assertEquals(3, receivedTasks.size());
  268.         /*
  269.             это нужно уточнить - првоерять не только количество тасок
  270.             а вообще все таски
  271.             assertEquals - для сравнения списков тасок - вполне ок будут работать - т к в классе Task -
  272.             реализованы equals & hashcode
  273.  
  274.             разумно реализовать в ресурсе - assertTasks(Task... tasks)
  275.             и внутри метода - получили гет-запросом таски
  276.             и сравнили с переданными с метод
  277.  
  278.             и тебе бы больше пригодилось - если бы тестовые данные были массивом, а не списком
  279.             причем - final-массивом
  280.             дочитай ревью - думаю - сообразишь - как final-массив применять
  281.         */
  282.     }
  283. *********************************
  284. putRequest(int taskId, Task task)
  285. /*
  286.     а не надо ид передавать отдельно и потом урл собирать
  287.     ведь у нас uri в Task task - уже содержится
  288. */
  289. ***********************************
  290. deleteRequest(int taskId)
  291. /*
  292.     а тут передавать ид - вполне уместно и экономно
  293.     т к кроме ид - и не надо ничего
  294. */
  295. ***************************************
  296.     @Test
  297.     public void testUpdate() {
  298.         List<Task> expectedTasks = new ArrayList<Task>();
  299.         expectedTasks.addAll(defaultTasks);
  300.         expectedTasks.get(0).setTitle("updated title");
  301.  
  302.         Response response = getRequest();
  303.  
  304.         Task task = getTasksFrom(response).get(0);
  305.         task.setTitle("updated title");
  306.  
  307.         Response putResponse = putRequest(1, task);
  308.         assertEquals(expectedTasks.get(0), getTaskFrom(putResponse));
  309.  
  310.         List<Task> receivedTasks = getTasksFrom(getRequest());
  311.  
  312.         assertEquals(expectedTasks, receivedTasks);
  313.     }
  314. /*
  315.     сложновато получилось
  316.  
  317.         делаем новый список из тасок по умолчанию
  318.         меняем таске что-то там
  319.  
  320.         делаем гет-запрос
  321.         меняем таске из респонса тайтл
  322.  
  323.         только потом делаем put
  324.  
  325.         потом сравниваем списки
  326.  
  327.     а можно и вот так
  328. */
  329.     @Test
  330.     public void testUpdate() {
  331.         Task task = new Task("t2 title", "t2 description", true, TasksApi.uri + "/2");
  332.  
  333.         TasksApi.update(task);
  334.  
  335.         TasksApi.assertTasks(DEFAULT_TASKS[0], task);
  336.     }
  337. /*
  338.     ну, если с респонсом работать в тест-методе -
  339.     то, конечно, чуть сложнее
  340.     но все равно попроще, чем сейчас
  341. */
  342. *************************************
  343. /*
  344.     я тесты запускала
  345.  
  346.         когда запускаешь только testDelete() - все ок
  347.  
  348.         а вот когда все тесты - проблемы
  349.         когда реализуешь toString() для класса Task - сообщения об ошибках будут нагляднее
  350.  
  351.     а если закомментить тесты с редактированием таски
  352.        то все ок будет работать)
  353.        так что - ищи корни проблем там
  354. */
Add Comment
Please, Sign In to add comment