Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- http://joxi.ru/8An6LoZhq5RKJA
- /*
- 1 - мы оперируем идентичными данными фактически
- Не надо в данном методе сервиса - дублировать коллекцию tasks
- Нужно грамотно воспользоваться тем, что уже есть в коде
- Если начальное состояние фиксировать в некой коллекции
- default_tasks, при старте и ресете - копировать в tasks содержимое default_tasks
- В 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
- 2 - в новом методе можно воспользоваться уже существующим методом
- get_tasks() (после того, как скопировать в tasks содержимое default_tasks)
- */
- http://joxi.ru/Y2LXgYnfnKLq02
- /*
- вот еще источник проблем)
- если мы создаем первую таску (например, перед этим - все таски удалили)
- то получим проблему)
- не будет у нас предыдущего элемента)
- все же - если элементов в коллекции тасок нету - то у первой таски должен быть ид=1
- https://lancelote.gitbooks.io/intermediate-python/content/book/ternary_operators.html
- https://lancelote.gitbooks.io/intermediate-python/content/book/comprehensions.html (dict абстракции)
- http://www.diveintopython.net/native_data_types/index.html#odbchelper.dict
- */
- *********************************************************
- public class RESTfulTasksTest {
- /*
- по правилам CamelCase - будет не так)
- https://google.github.io/styleguide/javaguide.html#s5.3-camel-case
- */
- TasksPage page = new TasksPage();
- /*
- посмотри конец видео про этот момент
- нам нужен не пейдж-объект
- а ресурс-объект
- примерно с 01:17:00
- Теперь у нас будет пекедж не pages, a resources.
- А правила работы с таким классом - аналогичные.
- Поскольку мы работаем только с тасками - в имена методов-действий можно не включать слово Tasks
- Имена методов-проверок - по-прежнему указываем точно
- Логично было бы назвать класс TasksApi
- (можно реализовывать как ресурс-модуль, а не ресурс-объект - как ты помнишь -
- таким образом код может стать немного проще)
- И в коде потом можно без статического импорта писать
- Response response = TasksApi.create(URI, "give lesson");
- Это было бы и наглядно, и с прицелом на будущее - на случай,
- если в тесте появятся запросы к другим ресурсам (и тогда будет несколько ресурс-модулей)
- */
- ********************************************************************
- public class TasksPage {
- public static final String URI = "http://localhost:5000/todo/api/v1.0/tasks";
- /*
- Еще было бы полезно вынести http://localhost:5000
- в Сonfiguration.baseUrl - в котором именно домен был сохранен
- чтобы его можно было от случая к случаю реконфигурить...
- Ведь это как раз та часть урла - которая не принадлежит ресурсу, и зависит от того,
- на каком сервере/по-какому-адресу ресурс/веб-сервис задеплоен
- На самом деле здесь есть несколько способов как лучше сделать
- Первый, если ты уверена, что тебе не нужно будет одновременно/паралельно посылать запросы на разные урлы
- То тогда можно использувать Configuration.baseUrl типа обычная глобальная переменная
- если нет, тогда лучше "ооп" подход, когда обьект-ресурс помнит свой базовый урл (доменную часть)
- который передается при создании ресурса-объекта через конструктор
- */
- *************************************
- public static final List<Task> defaultTasks = new ArrayList<Task>();
- /*
- как и в пейджах, так и в ресурсах - мы не будем держать тестовых данных
- и контейнеров под них = тоже не будем создавать на уровне ресурса
- тут действительно - будет полезным вынести такие данные в переменные - т к
- они всегда одни и те же и достаточно громоздкие. Но делать это нужно не на уровне ресурса
- С таким вынесением тестовых данных в переменные надо быть осторожным
- Важно - не надо тестовые данные объявлять в ресурс модуле
- (опять аналогия с пейджом - мы и там такого правила придерживались)
- Тут и правда есть смысл объявить эти данные
- Часто тестовые данные разумно оставлять в логике самих тестов -
- таким образом код тестов более очевидный. Пример - тудуэмвиси тесты
- */
- ********************************************************
- public Invocation.Builder requestTo(String uri) {
- return ClientBuilder.newClient().target(uri).request();
- }
- public Invocation.Builder authorized(Invocation.Builder requestBuilder) {
- return requestBuilder.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("miguel:python".getBytes()));
- }
- public Invocation.Builder authorizedRequestTo(String uri) {
- return authorized(requestTo(uri));
- }
- /*
- Вот эти методы действительно выполняют роль универсальных REST-хелперов -
- и их действительно разумно отделить в специальный класс-контейнер универсальных метдов RestHelpers
- Единственное, что authorized - стоит реализовать универсальнее -
- чтобы он получал логін:пароль как параметр
- А потом уже в нашем ресурс модуле
- создать метод authorizedRequest(...), который будет вызывать
- return authorized(“miguel:python”, requestTo(...))
- Нужно такие нюансы видеть и выделять универсальный код, который не зависит от нашего продукта
- Вот такой универсальный код принято размещать в пекедже core
- */
- ********************************
- public Response sendGetRequest(String uri) {
- return requestTo(uri).get();
- }
- public Response sendAuthorizedGetRequest(String uri) {
- return authorized(requestTo(uri)).get();
- }
- /*
- сравни вызовы
- sendGetRequest(uri);
- requestTo(uri).get();
- sendAuthorizedGetRequest(uri);
- authorized(requestTo(uri)).get();
- authorizedRequestTo(uri).get(); - этот вариант получше - обрати на него внимание
- выигрыш - 1-2 символа
- плюс к этому - добавили сущностей
- это как раз пример того - когда заворачивание кода в метод уже мало что дает
- только сложность повышаем - т к с большим количеством методов придется разобраться
- прибей эти методы
- и перейди на использование
- requestTo(uri).get();
- authorizedRequestTo(uri).get();
- */
- ********************************
- public Response resetTasks() {
- return sendAuthorizedGetRequest(URI + "/reset");
- }
- /*
- поскольку не используешь результат метода - можно его реализовать и как
- public static void reset()
- (про употребление в именах методов-действий Task/Tasks - выше писала)
- */
- ************************************************
- public List<Task> availableTasks() {
- Response response = authorized(requestTo(URI)).get();
- return response.readEntity(TasksContainer.class).getTasks();
- }
- /*
- что мы тут делаем - мы делаем get - для тасок
- вот и стоит также метод назвать - get()
- не надо вводить лишних терминов
- код можно переписать в одну строку
- return authorizedRequestTo(URI).get().readEntity(TasksContainer.class).getTasks();
- мне кажется - все еще ок
- это на твое усмотрение
- */
- ********************************************************
- public Response sendAuthorizedPostRequest(String uri, Task task) {
- return authorized(requestTo(uri)).post(Entity.entity(task, MediaType.APPLICATION_JSON));
- }
- /*
- что мы делаем - мы создает таску
- причем - мы тут оперируем тем же URI
- упрощаем имя метода и набор параметров
- public static Response create(Task task)
- а если еще немного упростим -
- public static Task create(Task task)
- Правильный статус ответа на каждый из запросов - строго определенный
- И если мы будем проверять статут ответа в тест-методе -
- то надо об этом думать и вспоминать - какой статус в каких случаях верный
- И можно выкрутиться - проверять статус ответа в нутри степа,
- а возвращать собственно entity из запроса.
- Сразу пара потенциально полезных моментов)
- Если встроить проверку response кода прямо в сам степ
- Тут сразу появляется интересный нюанс
- Дело в том, что мы так спрячем тестовую логику внутрь степа, что не очень хорошо
- С другой стороны - эта проверка = само собой разумеющееся
- Эта проверка достаточно стандартная и всегда будет повторяться после вызова степа (если будет жить вне степа)
- А раз так - то почему бы и не спрятать?
- На самом деле - нет однозначного ответа на этот вопрос...
- Так - со спрятанной проверкой - код станет проще во всех тестах - но втанет до некоторой степени “магическим”
- Тут надо решать самостоятельно - как лучше )
- На твое усмотрение
- Еще момент
- В этих API тестах - самих тестов может быть не так уж и много -
- чтоб еще и заморачиваться над DRY кода...нужно и этот аспект учитывать
- Но если пойти этим путем = прятать в степе проверку статуса response, то код будет намного более удобным:
- */
- @Test
- public void testCreate() {
- Response response = Tasks.create("give lesson");
- assertEquals(201, response.getStatus());
- assertEquals("give lesson", titleFor(response));
- }
- vs
- @Test
- public void testCreate() {
- Task task = Tasks.create("give lesson");
- assertEquals("give lesson", task.getTitle());
- }
- **********************************************
- public Response sendAuthorizedPutRequest(String uri, Task task) {
- return authorized(requestTo(uri)).put(Entity.entity(task, MediaType.APPLICATION_JSON));
- }
- /*
- если и тут применить те же приемы - получим
- public static Task update(Task task)
- согласись - понятнее - что мы будем делать
- что до того - где брать uri - task.getUri() нам его вполне отдаст )
- */
- *************************************
- public Response sendAuthorizedDeleteRequest(String uri) {
- return authorized(requestTo(uri)).delete();
- }
- /*
- можно вот так реализовать метод
- public static void deleteById(int taskId)
- тут на твой выбор - что передавать в качестве параметра
- нам по мольшому счету нужен лишь uri таски
- а значит - лишь ID таски
- но можно,конечно и Task task, и String uri
- параметр реализовать
- если внутри метода проверять код ответа - то уже не нужно ничего возвращать
- */
- *********************************************************
- public List<Task> tasksFromResponse(Response response) {
- return response.readEntity(TasksContainer.class).getTasks();
- }
- public Task taskFromResponse (Response response) {
- return response.readEntity(TaskContainer.class).getTask();
- }
- /*
- как ты понимаешь, и эти методы потеряют актуальность -
- т к все результаты - ты получаешь из респонса уже в рамках методов-действий
- еще чуть проще код стал)
- */
- ***********************************************
- public void assertStatus(int statusCode, Response response) {
- assertEquals(statusCode, response.getStatus());
- }
- /*
- сравни
- assertEquals(statusCode, response.getStatus())
- assertStatus(statusCode, response)
- я не уверена - что такой метод нужен )
- если ты видишь в нем смысл
- то перенеси и его в RestHelpers
- по-моему - перебор)
- */
- *********************************************
- public List<Task> joinTasks(Task... tasksToJoin) {
- public List<Task> joinTasks(List<Task> tasks, Task... tasksToJoin) {
- public void assertEqualListsOfTasks(List<Task> expectedTasks, List<Task> actualTasks) {
- public void assertEqualTasks(Task expected, Task actual) {
- @Test
- public void testCreateTaskWithTitle() {
- ...
- page.assertStatus(201, response);
- page.assertEqualTasks(newTask, page.taskFromResponse(response));
- List<Task> expectedTasks = page.joinTasks(defaultTasks, newTask);
- page.assertEqualListsOfTasks(expectedTasks, page.availableTasks());
- }
- /*
- посмотри на информацию по этим линкам
- 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
- мы можем получить код проще
- public static void assertTasks(Task... expectedTasks) {
- List<Task> actualTasks = get();
- assertEquals(Arrays.asList(expectedTasks), actualTasks);
- }
- А если реализуешь метод toString для класса Task - и сообщение об ошибке будет более информативным.
- ну и если говорить о реализации проверок в testCreateTaskWithTitle()
- то можно ограничиться проверкой assertTasks
- и не проверять таску, полученную из респонса
- хотя, конечно, это тоже сильно зависит от многого
- даже если и надо проверять - assertEquals - и для одной таски будет ок работать,
- если уже нормально для списка тасок работает)
- */
- *********************************************************************
- @BeforeClass
- public static void initTasks() {
- //straight way to save our default tasks
- Task firstTask = new Task("Buy groceries", "Milk, Cheese, Pizza, Fruit, Tylenol", false);
- firstTask.setUri("http://localhost:5000/todo/api/v1.0/tasks/1");
- Task secondTask = new Task("Learn Python", "Need to find a good Python tutorial on the web", false);
- secondTask.setUri("http://localhost:5000/todo/api/v1.0/tasks/2");
- //List<Task> defaultTasks = new ArrayList<Task>();
- defaultTasks.add(firstTask);
- defaultTasks.add(secondTask);
- //tried this way also but got an error from IDEA
- // List<Task> defaultTasksNewWay = Arrays.asList(new Task("Buy groceries", "Milk, Cheese, Pizza, Fruit, Tylenol", false)
- // .setUri("http://localhost:5000/todo/api/v1.0/tasks/1"),
- // 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"));
- }
- /*
- можно проще
- просто - объявить и инициализировать массив
- public final Task[] DEFAULT_TASKS = {
- new Task("Buy groceries", "Milk, Cheese, Pizza, Fruit, Tylenol", false, TasksApi.uri + "/1"),
- new Task("Learn Python", "Need to find a good Python tutorial on the web", false, TasksApi.uri + "/2")
- };
- и нагляднее, и никаких методов и шагов
- */
- @Test
- public void testResetTasks() {
- Response response = page.resetTasks();
- page.assertStatus(200, response);
- page.assertEqualListsOfTasks(defaultTasks, page.tasksFromResponse(response));
- }
- /*
- а есть ли смысл в таком тест-методе
- если
- ресет делать в бифор-методе
- и реализовать один из тестов - для get() = получить таски
- то мы как раз проверим и reset, и get
- до некоторой степени - не важно - что за таски нам будут переданы в респонсе ресета
- главное - чтобы ресет происходил = в базе были именно такие таски = таски по умолчанию
- вот потому - можно и не реализовывать тест для ресета)
- */
- ***************************************
- @Test
- public void testReadTasks() {
- /*
- и тут в именах тест-методов можно сэкономить на слове Tasks
- с оглядкой на имя тест-класса)
- */
- page.resetTasks();
- /*
- этот гивен(предварительное действие) = уйдет в бифор метод
- этот гивен на самом деле такой важный, что лучше вообще не скрывать его
- (в классе-предке так точно не стоит)
- его желательно "видеть" в самом тесте
- часто даже не стоит такие вещи в @Before выносить, не смотря на то что код будет не DRY
- но будет еще более явным и очевидным)
- Хотя в этом случае наверное с @Before будет таки лучше )
- Тестов не много, они лаконичные
- @Before-метод размести в начале тест-класса - будет достаточно наглядно
- */
- Response response = page.sendAuthorizedGetRequest(URI);
- page.assertStatus(200, response);
- /*
- код ответа мы уже проверяем в методе get
- */
- page.assertEqualListsOfTasks(defaultTasks, page.tasksFromResponse(response));
- }
- /*
- вот в результате таких доработок - и получим вариант полаконичнее
- */
- @Test
- public void testRead() {
- List<Task> actualTasks = TasksApi.get();
- assertEquals(Arrays.asList(DEFAULT_TASKS), actualTasks);
- }
- *****************************************
- @Test
- public void testCreateTaskWithTitle() {
- ...
- List<Task> expectedTasks = page.joinTasks(defaultTasks, newTask);
- page.assertEqualListsOfTasks(expectedTasks, page.availableTasks());
- /*
- вот тут нам реализация assertTasks(Task... expectedTasks)
- особенно пригодится
- т к меньше потребуется телодвижений
- получишь
- TasksApi.assertTasks(DEFAULT_TASKS[0], DEFAULT_TASKS[1], new Task("give lesson", "", false, TasksApi.uri + "/3"));
- */
- }
- ***********************************************************************
- @Test
- public void testUpdateTaskTitle() {
- page.resetTasks();
- Task newTask = new Task(Helpers.getUniqueText("t "));
- /*
- большого смысла нету использовать getUniqueText
- на начало теста - известно - какие таски есть
- нету никаких предпосылок использовать уникальный текст
- */
- ********************************
- @Test
- public void testCreateUpdateDelete() {
- page.resetTasks();
- Task testTask = new Task("give lesson");
- Response response = page.sendAuthorizedPostRequest(URI, testTask);
- //page.assertStatus(201, response);
- //page.assertEqualListsOfTasks(page.joinTasks(defaultTasks, testTask), page.availableTasks());
- String addedTaskUri = page.taskFromResponse(response).getUri();
- testTask.setTitle("stop lesson");
- response = page.sendAuthorizedPutRequest(addedTaskUri, testTask);
- //page.assertStatus(200, response);
- //page.assertEqualListsOfTasks(page.joinTasks(defaultTasks, testTask), page.availableTasks());
- page.sendAuthorizedDeleteRequest(addedTaskUri);
- page.assertEqualListsOfTasks(defaultTasks, page.availableTasks());
- }
- /*
- сложновато как-то код читается)
- хорошо бы после каждого действия - проверить состояние тасок
- можно получить что-то такое
- */
- @Test
- public void testCreateUpdateDelete() {
- Task task = new Task("t title", "t description", false, TasksApi.uri + "/3");
- TasksApi.create(task);
- TasksApi.assertTasks(DEFAULT_TASKS[0], DEFAULT_TASKS[1], task);
- Task editedTask = new Task ("t title edited", "t description edited", true, TasksApi.uri + "/3");
- TasksApi.update(editedTask);
- TasksApi.assertTasks(DEFAULT_TASKS[0], DEFAULT_TASKS[1], editedTask);
- TasksApi.deleteById(3);
- TasksApi.assertTasks(DEFAULT_TASKS[0], DEFAULT_TASKS[1]);
- }
- *****************************************
- static class ErrorContainer {
- /*
- А эти классы как раз стоит разместить в пакете containers -
- т к по сути это и есть контейнеры для парсинга с JSON
- касается и других классов
- */
- ****************************************************
- Строки подсвечены красным в идее, при этом проект компилируется и раннится, в чем проблема?
- import java.util.Base64;
- return requestBuilder.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("miguel:python".getBytes()));
- /*
- у меня все ок с твоим же проектом
- http://joxi.ru/vAW36KgskJl5oA
- может быть - что-то связано с версиями джавы
- я сначала делала на джаве помладше это задание
- см на код и на закомменченный код
- */
- //import org.jboss.resteasy.util.Base64; //for java7 & up
- import java.util.Base64;//since java 1.8
- public class RestHelpers {
- public static Invocation.Builder requestTo(String uri) {
- return ClientBuilder.newClient().target(uri).request();
- }
- public static Invocation.Builder authorized(String credentials, Invocation.Builder requestBuilder) {
- //return requestBuilder.header("Authorization", "Basic " + Base64.encodeBytes(credentials.getBytes()));//for java7 & up
- return requestBuilder.header("Authorization", "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes()));
- }
- }
- ******************************************************************************
- надо ли, реализуя паттерн page object, называть тут объекты не пейджами, а ресурсами:
- TasksResource resource = new TasksResource()
- /*
- это я выше ответила
- да, надо
- смысла реализовывать ресурс-объект - особо нет
- можно реализовать ресурс-модуль
- это так, лирическое отступление
- На реальном проекте обычно создают класс-ресурс (объект или модуль) не сразу, когда начали писать тесты.
- А позже - когда появится необходимость переиспользовать вспомогательные методы в разных тест-классах.
- Или если одни более квалифицированные члены команды разрабатывают класс-ресурс,
- а другие - используют для разработки тестов. Но или если так принято в данной команде)
- Т е - это делается обычно при необходимости. С пейджами - в общем аналогично.
- */
- ************************************************
- Когда я проверяю результаты выполненного запроса, я использую свой метод assertTaskListEqual(),
- но у меня еще есть отдельная проверка статуса респонса.
- Нужна ли она вообще? Если да, хорошо бы ее запихнуть в тот же метод assertTaskListEqual(),
- но как лучше - передавать параметром в метод - вроде как усложнять его?
- Написать для проверки статуса отдельный метод? Но тогда проверка результатов будет в две строчки...
- Сделала пока так: page.assertStatus(200, response);
- но очень хочется, чтобы была запись типа response.assertStatus(200); или просто assertStatus(200);...
- /*
- ага, и эту мысль ты учуяла)
- да
- проще позапихивать проверку кода ответа в сами методы-действия
- про недостатки такого решения было выше
- в общем-то - оправданая мера
- и методов лишних не нужно будет)
- */
- ***
- В целях улучшения кода я бы назвала assertEqualListOfTasks(expectedTasks, actualTasks)
- вот так: assertList(), но тогда не совсем в паре выглядит метод assertEqualTasks
- (его б тогда назвать assertEqual)
- Или назвать их просто assertListOfTasks, assertTasks, или даже assertTasks и assertTask?
- /*
- у тебя будет assertTasks
- а для проверки одной таски - я бы использовала стандартный junit-овский assertEqual
- он ничем не хуже - просто, коротко и наглядно
- не вижу смысла это заворачивать
- писала выше - еще не факт - что вообще это стоит проверять...
- вопрос дискуссионный, конечно - от многого зависит
- как компромисс - такое проверять в фиче-тестах
- а в е2е - уже е проверять
- (тут - только состояние всего списа тасок - как более показательную проверку)
- */
- ***************************************************************
- Я сделала отдельные классы Task, TaskContainer, TasksContainer, может надо их не выносить в отдельные, а оставить внутри класса TaskPage, как было сделано с классом Task в ToDoMVCPage?
- /*
- выше написала - в один пекедж и отдельно от тест-класса
- не надо его загромождать)
- http://joxi.ru/EA4k7zEUDgM1z2
- что-то такое получишь)
- */
- *********************************************************************
- Для облешчения читабельности кода самих тестов можно завернуть получение респонса в отдельный метод типа
- Response sendGetRequest(String uri)
- Response sendPostReauest(String uri, Task task)
- Response sendPutRequest(String uri, Task task)
- сделала так, не лишнее?
- /*
- написала про это)
- думаю - лишнее)
- */
- ***
- sendAuthorizedGetRequest используется у меня чаще, намного,
- чем просто sendGetRequest, может переименовать их на sendGetRequest и sendUnauthorizedGetRequest?
- аналогично с post, put, delete. Будет меньше букв, но какб название метода не будет точно говорить,
- что он делает... Или будет?)
- /*
- да, на уровне класса-ресурса - можно имена методам дать полаконичнее
- часть методов - переселить)
- часть методов - убить)
- и получится что-то типа такого
- */
- public class TasksApi {
- public static String uri = Configuration.baseUri + "/todo/api/v1.0/tasks";
- public static Invocation.Builder authorizedRequest(String uri)
- public static void reset()
- public static List<Task> get()
- public static Task create(Task task)
- public static Task create(String taskTitle)
- public static Task update(Task task)
- public static void deleteById(int taskId)
- public static void assertTasks(Task... expectedTasks)
- }
- /*
- т е - оперируем - простыми лаконичными словами )
- */
- ***
- А может надо от методов типа sendAuthorizedPostRequest вообще перейт к методам addTask/EditTask/deleteTask,
- тогда вообще все красиво, но мы ведь вроде АПИ тестируем, т.е. запросы тестируем, а не UI... Как тут правильнее?
- /*
- ага, только рассказала про это )
- то, что мы тестируем запросы - опредетяется еще на уровне самого теста
- можно проект как-то назвать - что это API Tests
- если в одном проекте тесты и для Web UI, и для API Tests
- то я бы на уровне пекеджей это разграничила
- да и имя тест-метода - тоже говорит нам про то - что и как мы тестим
- сильно дорого - каждый метод вот так сложно называть )
- потом смотришь на код - и приходится долго думать и вчитываться
- */
- ****
- появилась проблемка с uri:
- public void setUri(String uri) {
- uri = uri;
- } - почему-то сеттер был написан так, а не this.uri = uri,
- когда я его поменяла, то мой метод assertEqualTasks стал падать,
- потому что uri of actual task = реальному uri c индексом вконце,
- а uri of expected task = null......
- (ну да, мы же при создании объекта типа Task, uri никак не записываем,
- потому что мы не знаем, какой он будет, сколько там в базе реально уже тасков...)
- пока в ассерт-методе решила не сравнивать uri.
- /*
- в сеттере правильно - this.uri = uri
- нулла не будет - если будешь uri для таски прописывать
- на самом деле - все мы знаем)
- мы же сами ресет делали)
- и сами что-то делали после ресета)
- и знаем какой uri - т к видели как работает метод create )
- для нас это нормальная задача - благодаря ресету
- */
- ***************************************************************************************
- задумалась, как все-таки удобнее, я сделала ресет списка тасков до двух изначальных тасков.
- При таком способе сложнова-то проверять каждый раз, какие таски есть, зато для некоторых тестов не надо
- дополнительных приготовлений (для теста удаления, редактирования, например)...
- /*
- логичные размышления)
- был у нас уже такой диалог - что лучше
- как реализовать предварительые действия - delete all vs reset
- когда предварительные действия в тест-метода = 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 - как варианта, в котором меньшее количество вещей может сломаться
- и как более быстрого и более однозначного варианта
- */
- ***
- При отладке заметила следуюзую проблему:
- тест testUpdateTaskTitle падал (респонс возвращал статус 400 Bad Request).
- При этом аналогичный тест testUpdateFullInformation успешно проходил.
- Проблема была в содержимом передаваемого таска: в первом случае инициализировано было только поле title,
- description был null, а во втором и title, и description были заполненые нормальными стрингами.
- Тогда я в конструкторе Task(String title) дописала строку, чтобы полю description присваивалась пустая строка "".
- После этого тест стал проходить. Правильно ли я нашла и пофиксила проблему?
- /*
- да, правильно )
- я бы только для создания таски проверяла вариант с созданием по имени и по полной информации
- для апдейта - реализовывала бы один тест
- для таски
- мы же уже оперируем таской - мы ее собираемся редактировать
- можем ей все наменять - кроме uri
- вот и наменяй)
- */
- *******************************************************
- Надо ли писать дополнительные тесты создания таска не с json форматироваием, не с юникодом в тайтл-дескрипшен,
- с пустым тайтлом? вроде бы нет, т.к. это будет уже слишком много тестов,
- вроде как такое должно покрываться юнит-тестами
- /*
- да, это уже дело юнит-тестов
- ты права)
- */
- ************************************************************
- Нужен ли нам тест GET запроса для сервиса .../tasks/<id> - получение одного конкретного таска
- /*
- резонный вопрос)
- да, можно и это покрыть)
- */
- *************************************************
- В конце демо-лекци Яков произнес что-то про плагины для тестирования API?
- Надо попробовать все это еще в каком-то плагине?
- ЛИи я что-то не так поняла? Речь про restclient-idea-plugin?
- /*
- это про то - что не только с командной строки с curl можно работать
- погугли что-то типа chrome plugins curl
- речь про - как с curl можно работать легче и проще
- */
- *******
- Глобальный вопрос:
- В статье https://www.javacodegeeks.com/2011/10/java-restful-api-integration-testing.html описано,
- что именно надо тестировать в RESTful ресурсе:
- И сказано, что каждый тест должен содержать только один ассерт, т.е.
- Код ответа, заголовки и payload - это три отдельных теста.
- А у меня пока это все одн тест (и заголовки не тестируются никак).
- Дальше там вот что написано:
- This is a rather simple test,
- which verifies that a basic happy path is working,
- without adding to much complexity to the test suite.
- If, for whatever reason it fails, then there is no need to look at any other test for this URL until this is fixed.,
- логично, конечно же. МОжет и нам надо так сделать? :-)
- /*
- интересная статья)
- автор кстати и сам пишет
- Focusing on a clear separation always has benefits,
- but when doing this kind of black box testing it’s even more important,
- as the general tendency is to write complex test scenarios in the very beginning. (я про эту строчку)
- в принципе - мы из этих же соображений писали мало е2е и много фиче-тестов - чтоб они были независимыми
- и мы получили максимально полный фидбек от тестов - вне зависимости от того, что не работает
- нам - точно не надо так делать)
- если тебе понадобится так сделать - все знания для этого у тебя уже есть
- тут - достаточно тонкая грань)
- когда мы детализируемся слишком, даже если все структурно продумано
- количество сопровождаемых тестов - будет сильно расти
- надо каждый раз думать про цену)
- например, в таком-то запросе - нас не устроил код ответа
- вот исправили ошибку
- и логично было бы - проверить не только код ответа)
- но и что вернул запрос)
- а также - каково истинное положение дел после выполнения запроса
- а теперь представь - что на это у тебя несколько тестов)
- и тебе надо сообразить - какие тесты надо проверить
- а т к этих тестов - вообще очень много - придется покопаться)
- допускаю - что существуют такие ситуации - когда стратегия, описанная в тратье - будет как раз
- то, что надо
- надо каждый раз примерять это на цели и задачи - а стоит ли, а оправдано ли
- Якова еще поспрашиваю про это)
- хороший вопрос задала)
- */
- ******
- Еще нашла симпатичную вещь вот тут
- https://blog.philipphauer.de/testing-restful-services-java-best-practices/ :
- IntelliJ IDEA generates those setters for you.
- Moreover, there is a nice shortcut.
- Just write the fields (likeprivate String name;) and hit Alt+Insert , Arrow-Down
- until “Getter and Setter”, Enter ,
- Select “Builder” for “Setter template”, then Shift+Arrow-Down (multiple times) and finally press Enter.
- This is so cool!
- Afterwards you can use the fluent setters to write readable and typesafe code for creating test data.
- 1
- 2
- 3
- 4
- BlogDTO newBlog = new BlogDTO()
- .setName("Example")
- .setDescription("Example")
- .setUrl("www.blogdomain.de");
- Я сейчас использую два разных конструктора, но может так будет красивее и понятнее сам код теста?
- /*
- ну, для вот таких несложных случаев - я бы не применяла Builder Pattern )
- да, код красивее и понятнее, это да)
- можешь просто в рамках изучения паттерна попробовать это реализовать)
- в конце ревью приведу тебе полезное на почитать
- этот Builder Pattern - можно применять безотносительно того, что ты реализуешь
- в случаях - когда тебе надо что-то многокомпонентно и вариантивно собрать -
- будет хорошим решением
- */
- ******
- Еще отсюда: получается, аккуратнее и понятнее код был бы, если бы я в тесте оперировала бы не Response response
- переменной, а сразу уже десериализованным объектом типа Task.
- Но тогда будет не оч удобно проверять статус респонса….
- По последней ссылке используется какая-то дополнительная библиотека
- /*
- да, есть на нее линка в тексте
- хочешь - поразбирайся
- код и правда красивый
- как реализовать то, что ты описала в вопросе - я в ревью уже ответила )
- молодец, много в вопросах такого спросила)
- */
- *******************************************
- /*
- Про Builder Pattern - на простых примерах)
- https://jlordiales.me/2012/12/13/the-builder-pattern-in-practice/
- http://www.journaldev.com/1425/builder-design-pattern-in-java
- По этим линкам - по моему мнению - неплохо и несложно разъяснено про паттерн Builder
- по второй линке - вообще пошагово
- еще и это пригодится
- вот неплохие линки - про использование this
- https://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html
- http://stackoverflow.com/questions/2411270/when-should-i-use-this-in-a-class
- буду иносказательной и я)
- У нас будет класс = то, что мы строим
- и его внутренний класс (а если точнее - nested static class) = builder = как мы это строим
- мы хотим строить пошагово - сначала рассказать в сколь угодно много шагов(любая их комбинация)
- а потом - по собранной в шагах информации - построим наш объект
- и поскольку - чтобы строить объект - мы применяем класс-строитель
- то - конструктор для нашего объекта - не нужно делать public
- Допустим, мы делаем некий напиток
- мы можем добавлять
- воду
- сахар
- красители
- алкоголь
- фрукты
- паковать это в некую тару
- ну и вот это все в комплексе - и есть создание напитка
- вот и создаем класс Drink = т к делаем мы напиток
- и у него внутри - реализуем и класс-строитель = Barman
- public class Drink {
- private int water;
- private int sugar;
- private List<Colour> colours;
- private List<Alcohol> alcohols;
- private List<Fruit> fruits;
- private Bottle bottle;
- /*
- все свойства конечного объекта - описываем как private поля объекта
- */
- private Drink(Barman barman) {
- water = barman.water;
- sugar = barman.sugar ;
- colours = barman.colours;
- alcohols = barman.alcohols;
- fruits = barman.fruits;
- bottle = barman.bottle;
- ...
- }
- /*
- конструктор этого класса = тоже private
- и в качестве параметра он получает как раз объекта-строителя
- в конструкторе - переносим данные из строителя - в поля нашего объекта
- и далее - (см "..." в коде) при необходимости как-то работаем с данными
- */
- public static class Barman {
- private int water;
- private int sugar;
- private List<Colour> colours;
- private List<Alcohol> alcohols;
- private List<Fruit> fruits;
- private Bottle bottle;
- /*
- у класса-строителя - такой же набор полей
- и они тоже private
- все методы этого класса, кроме одного(build()) -
- как раз и будут собирать эти свойства
- */
- public Barman(Bottle bottle) {
- this.bottle = bottle;
- colours = new ArrayList<Colours> ();
- ...
- }
- /*
- конструктор класса-строителя - объявляем как public
- здесь мы инициализируем все поля этого класса
- если это необходимо - можно реализовать конструктор с параметрами
- цель - передать то, что будет обязательным для заполнения
- */
- public Barman addWater(int water) {
- this.water += water;
- return this;
- }
- /*
- каждый из методов, заполняющих какое-либо из свойств - это public метод
- и возвращает такой метод самого объекта-строителя
- */
- public Barman addFruits(Fruit... fruits) {
- for (Fruit fruit:fruits) {
- this.fruits.add(fruit);
- }
- return this
- }
- /*
- заметь - такие методы не заново заполняют соответствующие поля - а лишь дополняют то,
- что мы изначально задали в конструкторе
- цель - иметь возможность многократно и в любой последовательности вызывать методы строителя
- для каждого из свойств - реализуем такого плана метод
- */
- public Drink build() {
- return new Drink(this);
- }
- /*
- а этот метод класса-строителя - возвращает новый объект - который мы как раз строим
- и передаем в конструктор - this = наш объект-строитель
- собственно - фактически это и будет создание нашего объекта Drink
- (вспомни - что мы делали в конструкторе класса Drink)
- */
- }
- }
- /*
- если минимально - то это все)
- теперь мы можем создать нашего строителя
- new Drink.Barman(bottle)
- а чтоб эту часть кода сделать нагляднее - можем в классе Drink
- реализовать static метод, возвращающий новый объект типа строитель
- например
- */
- public class Drink {
- ....
- public static Barman composeDrink(Bottle bottle) {
- return new Barman(bottle);
- }
- public static class Barman {
- ...
- }
- }
- /*
- и далее в коде
- применив import static для composeDrink -
- писать
- */
- composeDrink(bottle)
- а не
- new Drink.Barman(bottle)
- /*
- ну и далее - начинается самое интересное
- composeDrink(bottle) - возвращает объект-строитель
- у которого много методов, "доливающих" наш напиток = уточняющих наш рецепт
- а т к каждый из этих методов - возвращает тот же объект
- мы можем строить любые цепочки вызовов
- и заканчивать такую цепочку - вызовом build
- только в вызове build - и будет построен объект
- согласно тому - что мы уточнили ранее
- */
- composeDrink(bottle).addWater(...).addAlcohol(...).addFruits(...).addColor(...).addFruits(...).build(); - некий коктейль )
- ...
- composeDrink(bottle).addWater(...).build(); - просто бутылка с водой
- ...
- composeDrink(bottle).build(); - вообще пустая бутылка )
Add Comment
Please, Sign In to add comment