Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- http://joxi.ru/nAyqEx7HX63q9A
- красиво придумал поправить метод создания тасок)
- http://joxi.ru/Dr860ybhkgybn2
- Ну, технически - можно и так)
- Только тут у нас на самом деле есть варианты
- условно назовем вариант delete all
- (тобой реализованный вариант)
- когда предварительные действия в тест-метода = delete all + add some tasks
- и вариант reset = когда при его вызове - снова получаем те же 2 таски, что были на начало теста...
- Delete all
- В такой реализации - код удаления всех тасок без каких-то сложностей особенных.
- Это плюс. А минус в том, что для гивен-методов тогда надо реализовывать дополнительное что-то.
- На начало тест-метода часто нужны существующие таски - нам же надо реализовать тесты для апдейта или удаления.
- Судя по реализации гивенов - когда в гивенах создаются таски - то используется сначала delete all,
- а потом добавление тасок.
- Т е действий - больше, чем если бы мы реализовали reset.
- Если deleteAll or add отвалились - то гивены не работают.
- Хотя, с другой стороны, если reset отвалился - тоже беда)))
- Но, в таком случае, все же это меньшее зло,
- т к этот reset служит только для тестовых целей
- и с развитием сервера он вряд ли будет меняться,
- да и логика у него и у так реализованных гивенов - значительно проще...
- Более того, с reset получше вероятности по надежности гивенов -
- с reset только одно звено (примитивное),
- а с delete all - два (реально использующихся в рабочих целях = более сложных = add + delete all)
- Еще такой момент.
- При тестировании операции add - вызывать гивен, в котором уже вызван add - как-то странно)
- Т е при таких раскладах - надо вызывать гивен строго в ситуации, кгда тасок мы не добавляем
- Это конечно не KISS тоже - то, что надо помнить о каких-то ограничениях
- Из этой же серии
- delete all - тоже операция, которая может быть востребованой в работе приложения
- Значит - и ее надо тестировать)
- А раз так - для delete all вызываем гивен, в котором вызываем delete all
- Тут уже идея с delete all в качестве вспомогательной операции - заходит в тупик)
- С Delete all хорошо то, что гивены гибкие и можно более интересную тестовую ситуацию создать.
- правда, вопрос - кому она тут нужна)))
- т к тут, на этом этапе - достаточно простые вещи нам нужно проверить
- супер-навороченых гивенов в общем-то и не надо
- вывод - = с reset = 2 таски по дефолту более KISS получается)
- если бы стояла задача в REST API тестах прогнать работу с тасками -
- у которых тексты разной длины/кодировки ....,
- то может - навороченные гивены, использующие delete all, были бы и нужны.
- да и то - не факт )
- И вот это очень серьезное замечание - когда delete all тестируется используя в предварительніх действиях саму себя
- Я - сторонник варианта с reset - как варианта, в котором меньшее количество вещей может сломаться
- и как более быстрого и более однозначного варианта
- так что - лучше избавься от delete all и от сложных гивен-методов
- и реализуй reset. И далее - перед каждым тест-методом просто его используй - всегда получишь одинаковую начальную тестовую ситуацию.
- Нужно грамотно воспользоваться тем, что уже есть в коде
- Если начальное состояние фиксировать в некой коллекции tasks_default,
- а при старте и ресете - копировать в tasks содержимое tasks_default
- В python есть способ - скопировать содержимое одного списка в другой с помощью одной операции,
- а не переносить каждый элемент списка отдельно
- Полезные линки
- http://www.python-course.eu/python3_global_vs_local_variables.php
- https://docs.python.org/2/library/copy.html
- http://joxi.ru/Dr8vxzPFk3Kl32
- http://stackoverflow.com/questions/2612802/how-to-clone-or-copy-a-list-in-python
- http://joxi.ru/J2b9KdBt41qEpm
- *************************************************************
- http://joxi.ru/p275M9zs0q5Gqr
- Технически - класс Task - не контейнер
- но в общем - конструкция некоторых контейнеров
- на этих основаниях можно его в пекедже containers разместить
- возле тест-класса - совсем ни к чему он )
- **************************************************************
- public class Task {
- private int id;
- private String title;
- private boolean done;
- private String description;
- private String uri;
- /*
- посмотри - что нам приходит в качестве тасок
- (через System.out.println(.....readEntity(String.class));
- получаем
- {
- "tasks": [
- {
- "description": "Milk, Cheese, Pizza, Fruit, Tylenol",
- "done": false,
- "title": "Buy groceries",
- "uri": "http://localhost:5000/todo/api/v1.0/tasks/1"
- },
- {
- "description": "Need to find a good Python tutorial on the web",
- "done": false,
- "title": "Learn Python",
- "uri": "http://localhost:5000/todo/api/v1.0/tasks/2"
- }
- ]
- }
- т е - id - не получишь)
- в статье про этот момент тоже написано
- https://habrahabr.ru/post/246699/ раздел Улучшаем интерфейс нашего сервиса
- избавляйся от id)
- еще по конструкторам
- реально - ты далеко не все конструкторы используешь
- оставь конструкторы
- без параметров
- с заполнением title (тут - остальные поля заполни по умолчанию,
- ну - или сделай это - когда увидишь проблемы со сравнением тасок)
- c заполнением всех реквизитов
- конструктор с заполнением title - нам будет нужен для теста создания таски, которой мы заполняем только title
- */
- public static Response create(Task task) {
- public static Response update(int taskID, Task task) {
- public static Response delete(Task task) {
- public static Task get(Response response) {
- ...
- /*
- Ну... буду ругаться )))
- ты же уже в курсе про принцип Single Responsibility)
- Мы ж пока фреймворк для работы на селениуме делали - про это мноого говорили
- у тебя получилась смесь бульдога с носорогом)
- с одной стороны - структура для контейнеров
- а с другой - класс-ресурс
- давай ты структуру для контейнеров оставишь именно этим
- а в класс-ресурс вынесешь то, что относится именно к нему
- ниже - пояснения
- */
- /*
- Вспомогательные методы разумно вынести в аналог пейджа.
- Теперь у нас будет пекедж не pages, a resources. А правила работы с таким классом - аналогичные.
- Поскольку мы работаем только с тасками - в имена методов-действий можно не включать слово Tasks
- Имена методов-проверок - по-прежнему указываем точно
- Логично было бы назвать класс Tasks (будем реализовывать как ресурс-модуль, а не ресурс-объект)
- И в коде потом можно без статического импорта писать
- Response response = Tasks.create(URI, "give lesson");
- Это было бы и наглядно, и на будущее - на случай, если в тесте появятся запросы к другим ресурсам
- (URI - тоже можно было бы разместить в классе-ресурсе. Потом и это подрихтуем немного)
- Варианты имени для класса-ресурса
- Tasks - неплохое имя
- но у нас есть класс Task
- и можно сказать - что мы используем один термин для достаточно разных понятий….
- Можно в таком случае для класса-ресурса использовать TasksApi или TasksRest в таком случае…
- Но и Tasks - тоже неплохо…, но хуже)
- Можно пойти на эту уступку в принципе - код понятный, названия наглядные
- Когда на реальном проекте обычно создают класс-ресурс (объект или модуль)
- На реальном проекте обычно создают класс-ресурс (объект или модуль) не сразу, когда начали писать тесты.
- А позже - когда появится необходимость переиспользовать вспомогательные методы в разных тест-классах.
- Или если одни более квалифицированные члены команды разрабатывают класс-ресурс,
- а другие - используют для разработки тестов. Но или если так принято в данной команде)
- Т е - это делается обычно при необходимости. С пейджами - в общем аналогично.
- */
- public static boolean assertTasks(Task task, Task task1) {
- public static boolean assertTitles(Task task, Task task1) {
- public static boolean assertDescription(Task task, Task task1) {
- public static boolean assertDone(Task task, Task task1) {
- /*
- ассертам тоде не место в классе Task
- сами ассерты будем серьезно дорабатывать)
- */
- /*
- аналогично - по другим классам-контейнерам
- пусть останутся только контейнерами)
- */
- **********************************************
- public class Helper {
- /*
- точнее было бы RestHelpers
- херперЫ для REST-а
- тут - место для универсальных методов
- никак не привязанных с особенностями конкретного приложения
- */
- public static Invocation.Builder requestTo(String uri) {
- public static Invocation.Builder authorized(String login, String password, Invocation.Builder requestBuilder) {
- /*
- эти 2 метода - действительно универсальные методы, методы для RestHelpers
- */
- public static Invocation.Builder authorized(Invocation.Builder requestBuilder) {
- return authorized(TestData.LOGIN, TestData.PASSWORD, requestBuilder);
- }
- /*
- А это - уже не здесь)
- это уже стоит реализовывать в классе-ресурсе
- насчет логина-пароля и их производых
- этот сервис подразумевает каких-то других юзеров, кроме miguel?
- если бы да - то я согласилась бы с тобой, что стоило бы реализовать TestData
- в котором есть информация о юзерах
- вывод - нам TestData не нужен
- достаточно будет в классе-ресурсе в реализации authorized - зашить "miguel:python"
- */
- public static Response givenTasks(Task... tasks) {
- public static void givenEmptyList() {
- /*
- из написанного выше - не нужны тебе гивен-методы
- это раз
- и им точно не место в RestHelpers
- если бы и были они нужны - то реализовывать их стоило в классе-ресурсе
- */
- **********************************************
- /*
- Что же там (в классе-ресурсе) у нас будет
- наш URI "http://localhost:5000/todo/api/v1.0/tasks" - как раз URI для ресурса tasks
- значит - тут и разместим
- на самом деле - и с URI можно еще кое-что улучшить
- но пока не будем распыляться - сделаем при следующих ревью
- практически для всех запросов к серверу нам нужно быть авторизированными
- поскольку только в рамках этого класса мы реализуем методы, которые будут делать такие запросы
- то тут и разместим метод, который будет делать авторизированный запрос для "miguel:python"
- воспользуйся уже реализованным универсальным методом, передав туда в качестве параметра "miguel:python"
- подумай, какой параметр должен быть у метода и что он должен возвращать
- а дальше - реализуем надор методов, работающих с ресурсом
- поскольку работаем с тасками - уже в именах методов-действий это не уточняем
- методы-действия
- - получить таски
- - создать таску (2 метода - только по заголовку или по всей информации)
- - обновить таску
- - удалить таску
- - выполнить reset
- метод-проверка
- - проверить таски (нам нужен такой метод, который будет сверять ожидаемый результат с фактическим - для всех тасок)
- Есть интересный момент
- Встраивать ли проверку статуса в метод-действие
- Если встроить проверку response кода прямо в сам степ
- Дело в том, что если так делать - то мы спрячем тестовую логику внутрь степа, что не очень хорошо
- С другой стороны - эта проверка = само собой разумеющееся
- Эта проверка достаточно стандартная и всегда будет повторяться после вызова степа (если будет жить вне степа)
- А раз так - то почему бы и не спрятать?
- На самом деле - нет однозначного ответа на этот вопрос...
- Так - со спрятанной проверкой - код станет проще во всех тестах - но код станет до некоторой степени “магическим”
- Тут надо решать самостоятельно - как лучше )
- На твое усмотрение
- Еще момент
- В этих API тестах - самих тестов может быть не так уж и много -
- чтоб еще и заморачиваться над DRY кода...нужно и этот аспект учитывать
- Но если пойти этим путем = прятать в степе проверку статуса response, то код будет намного более удобным:)
- Этот момент также повлияет на то, что каждый из методов нам будет возвращать
- */
- ****************************************
- public class RestfulTasksTest {
- /*
- по правилам CamelCase
- RestFulTasksTest
- https://google.github.io/styleguide/javaguide.html#s5.3-camel-case
- */
- public static final String URI = "http://localhost:5000/todo/api/v1.0/tasks";
- /*
- это уйдет в класс-ресурс - т к URI именно для тасок
- */
- Task task1 = new Task(1, "task1", false, "desc1", URI + "/1");
- Task task2 = new Task(2, "task2", false, "desc2", URI + "/2");
- /*
- Вместо этого, здесь же - с учетом реализации ресета
- объяви таски по умолчанию
- С таким вынесением тестовых данных в переменные надо быть осторожным
- Важно - не надо тестовые данные объявлять в ресурс модуле (опять аналогия с пейджом - мы и там такого правила придерживались)
- Тут и правда есть смысл объявить эти данные
- Часто тестовые данные разумно оставлять в логике самих тестов - таким образом код тестов более очевидный. Пример - тудуэмвиси тесты
- Но - если эти данные вынесены в переменные - то их и в таких случаях стоит использовать
- */
- @Test
- public void testUnauthorizedError() {
- Response response = requestTo(URI).get();
- assertEquals(403, response.getStatus());
- assertEquals(new ErrorContainer("Unauthorized access"), response.readEntity(ErrorContainer.class));
- /*
- стоило ли реализовывать equals для класса ErrorContainer - вопрос)
- ведь только тут это пригодилось
- и вот такой код сравни
- assertEquals("Unauthorized access", response.readEntity(ErrorContainer.class).getError());
- по-моему, он попроще таже будет)
- */
- }
- @Test
- public void testReadTasks() {
- givenTasks(task1, task2);
- List<Task> receivedTasks = TasksContainer.getReceivedTasks();
- /*
- getReceivedTasks - реализуем в классе-ресурсе
- и назовем его просто get
- TasksAPI.get() - вполне понятно, о чем
- согласна, прикольно, когда метод get вернет нам сразу список тасок
- но тогда в рамках метода стоит проверить статус (выше было про это)
- */
- assertTrue(assertTasks(task1, receivedTasks.get(0)));
- assertTrue(assertTasks(task2, receivedTasks.get(1)));
- /*
- про проверки
- вспомни - как ты проверял состояние тасок в тудуэмвиси
- ты сравнивал состояние ВСЕХ тасок - фактические и ожидаемые
- мы можем получить такого плана код
- public static void assertTasks(Task... expectedTasks) {
- List<Task> actualTasks = get();
- assertEquals(Arrays.asList(expectedTasks), actualTasks);
- }
- в метод передаешь ожидаемые таски
- в методе - получаешь запросом фактические таски и сравниваешь их
- А если реализуешь метод toString для класса Task - и сообщение об ошибке будет более информативным.
- метод - для класса-ресурса
- посмотри полезные линки (ты уже многое и реализовал для такого сравнения)
- http://www.java2s.com/Tutorial/Java/0140__Collections/CheckingforEquality.htm
- http://www.javapractices.com/topic/TopicAction.do?Id=17
- http://javadevnotes.com/java-array-to-list-examples
- */
- }
- @Test
- public void testCreate() {
- /*
- предыдущий метод называется testReadTasks()
- этот testCreate()
- думаю - про Tasks нужно уточнять в каждои имени метода или вообще не уточнять
- т к мі можем работать только с тасками
- мы реализуем 2 метода для создания таски
- 1 - для создания, когда мы задаем лишь title таски
- 2 - когда полную информацию
- */
- givenTasks(task1);
- Response response = Task.create(task2);
- /*
- метод create - реализуй внутри класса-ресурса
- он, кстати, по аналогии с методом get - может возвращать таску
- и внутри него - тоде можно проверить статус респонса
- (сейчас статус ты не всегда проверяешь)
- */
- Task taskFromResponse = Task.get(response);
- assertEquals(201, response.getStatus());
- assertTrue(assertTasks(task2, taskFromResponse));
- List<Task> tasks = TasksContainer.getReceivedTasks();
- assertEquals(2, tasks.size());
- assertTrue(assertTasks(task1, tasks.get(0)));
- assertTrue(assertTasks(task2, tasks.get(1)));
- /*
- тут, вместо 2-ух этих блоком - достаточно воспользоваться реализованным assertTasks
- по такой же схеме и следующие тест-методы строй
- */
Advertisement
Add Comment
Please, Sign In to add comment