Back to blog

Интересные особенности HTML, которые ты, возможно, и не знал


Что нового может быть в HTML? Казалось, что всё уже написано, рассказано и перерассказано. Но часто наблюдаю, как коллеги удивляются, казалось бы, простым общеизвестным вещам так, как будто это какой-то невероятный секрет.

Вот и собралась небольшая подборка таких HTML-штук. Если есть дополнения, пиши в чат или в личку.


Абсолютный путь без протокола

Https становится чуть ли не стандартом, однако многие сидят на http. И что делать, если нужен файл с другого сайта и не хочется зависеть от протокола.


Тэг label

Я, действительно, был уверен, что все это знают, но многие удивляются тому факту, что тэг label можно связать с input и нажатие на label будет ставить фокус или активировать/деактивировать input

Эту особенность очень удобно использовать для стилизации чекбоксов и радиокнопок, а также для создания интересных эффектов при фокусировании на input.


Атрибут contentEditable

Этот атрибут позволяет делать содержимое блока изменяемым.

Тэг optgroup для select

Меня удивляет, что многие вообще не в курсе про optgrup. Этот тэг позволяет группировать options внутри

Апельсиновый
Лимонный
Персиковый
Светлый
Нормальный
Темный


Тэг base

Тэг base определяется внутри head и говорит о базовом пути.

Если мы на странице site.com/articles поставим ссылку

контакты

, то эта ссылка будет вести на site.com/articles/contacts

А если же мы в head добавим

, то та же самая ссылка будет вести уже на site.com/contacts

У этого элемента есть атрибут target. И если установить его в target="_blank", то все ссылки будут открываться в новой вкладке.


Событие Onerror для тэга img

Onerror срабатывает перед тем как браузер покажет ваше изображение как битое. Можно на это событие повесить "самоуничножение" картинки из DOM. Для замены битой картинки на какую-то стандартную(noimage) или сообщение о "битости" всё же изящнее использовать :before и :after, но про это события я должен был упомянуть.


Тэг kbd

Этот тэг используется, в основном, для выделения названия компьютерных клавиш в тексте. Текст внутри такого тэга выделяется моноширным шрифтом.


Выделение удалённых частей текста

Для выделения части текста, которая со временем была удалена, используются тэги и . У них есть атрибуты cite и datetime. В первом указывается ссылка на причину удаления, а во втором дата удаления.

Полезно для изменяющихся с течением времени текстах. Например вышел закон, который делает недействительным какой-то из пунктов просматриваемого документа. Можно "завернуть" этот пункт в один из этих тегов и дать ссылку на закон и указать дату его принятия.


Гиперссылка для отправки e-mail

Я уверен, что все знают про

Написать мне

Если кто не знает, то эта ссылка открывает почтовый клиент по умолчанию, в котором открыто окно отправки нового письма и в поле "кому" уже вбит адрес, который указан после "mailto:" .

Но далеко не все знают, что можно пойти дальше:

href="malito:mail{at}yandex.ru?subject=Feedback&body=Тело20письма&cc=copy{at}yandex.ru&bcc=hiddencopy{at}yandex.ru" title="Ссылка на какой-то сайт">Сайт

Параметр subject - это тема письма,

body - текст письма,

cc - адрес, на который пойдёт копия

bc - адрес, на который пойдёт скрытая копия.

Зачем? Таким образом можно сформировать шаблон письма для обращения пользователей с сайта по email.


">

В html5 появилось много разных типов для input, однако мало кто использует тип search. Как можно догадаться, он используется для создания поля поиска. Правда единственное его отличие от обычного input type='text' лишь в том, что при наличии текста внутри в правой части появляется крестик для очистки содержимого поля.


_

Если ни один из пунктов тебя не удивил и ты знал всё вышеперечисленное, то ты мои поздравления, похоже, ты бог html. Но по моей личной статистике подавляющее большинство разработчиков, использующих в своей работе html, не знают и половины из этого списка.


Если есть дополнения, возражения или комментарии, то можно обсудить в клубе программистов.

Если не подписан на "Я - программист!", то подписывайся, а если подписан, то расскажи другу!

© 2020 shogenov.com


Тэг label

Я, действительно, был уверен, что все это знают, но многие удивляются тому факту, что тэг label можно связать с input и нажатие на label будет ставить фокус или активировать/деактивировать input

Эту особенность очень удобно использовать для стилизации чекбоксов и радиокнопок, а также для создания интересных эффектов при фокусировании на input.


Атрибут contentEditable

Этот атрибут позволяет делать содержимое блока изменяемым.

Тэг optgroup для select

Меня удивляет, что многие вообще не в курсе про optgrup. Этот тэг позволяет группировать options внутри

Апельсиновый
Лимонный
Персиковый
Светлый
Нормальный
Темный


Тэг base

Тэг base определяется внутри head и говорит о базовом пути.

Если мы на странице site.com/articles поставим ссылку

контакты

, то эта ссылка будет вести на site.com/articles/contacts

А если же мы в head добавим

, то та же самая ссылка будет вести уже на site.com/contacts

У этого элемента есть атрибут target. И если установить его в target=\"_blank\", то все ссылки будут открываться в новой вкладке.


Событие Onerror для тэга img

Onerror срабатывает перед тем как браузер покажет ваше изображение как битое. Можно на это событие повесить \"самоуничножение\" картинки из DOM. Для замены битой картинки на какую-то стандартную(noimage) или сообщение о \"битости\" всё же изящнее использовать :before и :after, но про это события я должен был упомянуть.


Тэг kbd

Этот тэг используется, в основном, для выделения названия компьютерных клавиш в тексте. Текст внутри такого тэга выделяется моноширным шрифтом.


Выделение удалённых частей текста

Для выделения части текста, которая со временем была удалена, используются тэги и . У них есть атрибуты cite и datetime. В первом указывается ссылка на причину удаления, а во втором дата удаления.

Полезно для изменяющихся с течением времени текстах. Например вышел закон, который делает недействительным какой-то из пунктов просматриваемого документа. Можно \"завернуть\" этот пункт в один из этих тегов и дать ссылку на закон и указать дату его принятия.


Гиперссылка для отправки e-mail

Я уверен, что все знают про

Написать мне

Если кто не знает, то эта ссылка открывает почтовый клиент по умолчанию, в котором открыто окно отправки нового письма и в поле \"кому\" уже вбит адрес, который указан после \"mailto:\" .

Но далеко не все знают, что можно пойти дальше:

href=\"malito:mail{at}yandex.ru?subject=Feedback&body=Тело20письма&cc=copy{at}yandex.ru&bcc=hiddencopy{at}yandex.ru\" title=\"Ссылка на какой-то сайт\">Сайт

Параметр subject - это тема письма,

body - текст письма,

cc - адрес, на который пойдёт копия

bc - адрес, на который пойдёт скрытая копия.

Зачем? Таким образом можно сформировать шаблон письма для обращения пользователей с сайта по email.


\">

В html5 появилось много разных типов для input, однако мало кто использует тип search. Как можно догадаться, он используется для создания поля поиска. Правда единственное его отличие от обычного input type='text' лишь в том, что при наличии текста внутри в правой части появляется крестик для очистки содержимого поля.


_

Если ни один из пунктов тебя не удивил и ты знал всё вышеперечисленное, то ты мои поздравления, похоже, ты бог html. Но по моей личной статистике подавляющее большинство разработчиков, использующих в своей работе html, не знают и половины из этого списка.


Если есть дополнения, возражения или комментарии, то можно обсудить в клубе программистов.

Если не подписан на \"Я - программист!\", то подписывайся, а если подписан, то расскажи другу!

","alias":"interesnye-osobennosti-html-kotorye-ty-vozmozhno-i-ne-znal","created":"2018-02-09 10:46:31","tags":null,"active":1,"excludeFromBlog":0},"list":[{"id":54,"title":"Вещи, которые каждый разработчик должен делать","description":"Девять вещей, которые помогут разработчику в работе и при трудоустройстве","header":"9 вещей, которые каждый разработчик должен делать","content":"


Быть упорным

Упорство - это , наверное, качество номер 1 для программиста. Каждый программист встречается с проблемами. Это часть работы и именно эта часть работы цениться выше всего. Нужно расти и становиться человеком, который может решить проблемы, которые другие не представляют как решать. Не важно, что это: проблема с кодом, с функциональностью или новая технология. Это не имеет никакого значения, просто сделай это. Во всём программировании нет никаких сакральных знаний, все решаемо и решается. Не нужны никакие специальные навыки или знания, просто упорство и желание решить проблему во что бы то ни стало. 

Хочется сдаться? Именно в этот момент нужно проявить большее упорство и сделать это.

Решение проблемы, которая еще недавно казалось тебе нерешаемой, вызывает неимоверный кайф и состояние эйфории. Даже если причиной этой проблемы стал ты сам 😉


Никогда не прекращать обучаться

Если ты хочешь расти как профессионал и чтобы зарплата тоже росла, нужно все время быть в процессе обучения, изучения нового.

Если не хочешь и счастлив быть разработчикам php5/JQuery, хорошо, каждый сам волен распоряжаться своей жизнью.

Но если у тебя есть хоть малейшая склонность двигаться вверх, ты должен быть готов учиться новым вещам. “Новыми вещами” могут быть новые языки программирования, технологии, подходы, методики и т п. Но лучше всего, чтобы эти новые вещи были каким-то образом связаны с твоим текущим стеком. К примеру, если программируешь на php/js/jquery изучи современный фреймворк react/angular/vue или посмотри в сторону reactphp

Это не только делает тебя более ценным для твоего нынешнего работодателя, но и дает тебе больше рычагов влияния при переговорах о компенсации и открывает больше возможностей, если ты решишь сменить работу. 



Оспаривать устоявшиеся вещи и задавать “Тупые” вопросы

Я не имею ввиду, что надо всегда пытаться быть против того, что уже есть в проекте. Просто любимым вопросом должен стать ”Почему?”.

Нужно ставить под вопрос текущие решения. Это может быть очень сложно и коллеги не будут тебя за это сильно любить, но инновации требуют остановиться и спросить “Почему?”.

Нет ничего хорошего в том, чтобы пытаться сохранить старые разработки или технологии, особенно, если они приносят проблемы. Либо вы потеряете свою основную цель и целью станет поддержание жизнеспособности того, что уже есть, либо стоить сделать шаг назад, выяснить почему все так сложилось и есть лучший способ сделать это.


Лучший путь для этого - это “тупые вопросы”. Не надо приходить и говорить “почему мы делаем так?”. Это может показаться грубым и, что хуже, непонятным.

Вместо этого лучше спросить “А есть какая-то особая причина, почему мы используем X для этой части программы?”. Смысл тот же, но это звучит понятнее показывает верное направление. 


Читать эти долбанные Логи/Мануалы/Документацию

Тебе нужно читать то, что тебе дают! 

Любое приложение, библиотека, модуль, язык или что-то еще ,скорее всего, имеют достаточную документацию. Даже если это что-то до безобразия простое, например простейшее API, тебе нужно сделать это.

Вместо того, чтобы часами гоняться за фантомной проблемой, перкомпилируя код, пересобирая бандлы, обновляя систему, нужно открыть и прочитать логи/сообщение об ошибке и документацию.


Логи, сообщения ошибок и документация пишутся именно для того, чтобы избежать проблем или, по крайней мере, скорее их решить.


Отвлекаться от работы.

Нет такого правила всё время сидеть за столом(фирмы, использующие трекеры, горите в аду). Если у тебя что-то не получается, ты в чем-то запутался или просто устал, если ты в буквальном смысле не прикован цепями к столу, то встань и прогуляйся. Сходи за кофе, придумай планы на выходные или напиши что-нибудь другу. Сделай что-то, что отвлечет тебя от работы. Человеческий мозг - странный орган, даже если в данный момент ты не думаешь о проблеме, мозг всё равно пытается решить её в фоновом режиме.

Часто потрясающие идеи приходят вовсе не на рабочем месте, а например в душе или в … кхм… в туалете, в общем, вне рабочего стола.

Это может звучать не очень логично, но мозг решает проблемы и в фоновом режиме: он всегда думает, всегда работает, так что дай ему время подумать и поработать.


Быть на связи

Я не говорю о том, чтобы быть онлайн 24/7 и по каждому чиху отвлекаться и отвечать. У команды должна быть возможность связаться с тобой. Не нужно следить за всеми менеджерами и соцсетями и отвлекать на каждое обновление. Оставь включенными только личные сообщения и упоминания в корпоративном мессенджере, я думаю, этого достаточно. Некоторые коллеги, могут этим злоупотреблять, беспокоя по “экстренному каналу“ по пустякам. Что ж, таким коллегам придется объяснить, что для таких вещей, есть, например таск-менджер. 


Выигрывать в команде, проигрывать одному

Взаимоотношения с коллегами - это очень важно. Так же важно как и взаимоотношения с начальством. Нужно выстроить и поддерживать хорошие рабочие отношения с коллегами. Особенно, если ты senior или выше. Тебе необходимо признавать успехи своих коллег, когда они делают что-то невероятное. И ты должен брать ответственность за ошибки, потому что ошибки будут. Никакой менеджер не будет винить тебя в то, что ты совершил ошибку (хороший менеджер не будет), все мы люди, очень плохо не учиться на этой ошибке. Если ты совершил промах, возьми на себя ответственность за это, запомни это и больше никогда не повторяй эту ошибку. Худшая вещь, которую ты можешь сделать, это винить в своих ошибках других членов своей команды. 


Совсем наоборот нужно поступать при успехе. Когда вы вместе с командой сделать что-то выдающееся, не надо забирать все лавры успеха себе. Если кто-то хочет тебя выделить, пожалуйста, пусть это сделает. Но приписывать себе одному успех всей команды - это ужаснейший поступок.


Избегать токсичных рабочих мест

В работе разработчика нет ничего важнее собственного благополучия. Да, именно так:


В работе разработчика нет ничего важнее собственного благополучия


Ни босс, ни коллеги, ни кусок кода, ни ценности компании не могут быть важнее тебя самого. Это совершенно нормально сменить работу, чтобы избавить себя от неприятной рабочей обстановки, если нет возможности сделать это внутри компании. Найди новую работу, напиши заявление и уходи на новую работу.

У меня была работа, при устройстве на которую мне обещали новейший стэк, полный карт-бланш в выборе технологий и проектировании структуры, адекватное руководство и т. п. На самом деле, было сложно согласовать установку последней версии языка, когда повсеместно а фирме использовался язык версии семилетней давности. Быстрые костыли были основным методом решения задач и приветствовались в отличие от мыслей о хотя бы легком рефакторинге. Задачи и “хотелки” летели от всех подряд: руководства, директоров, руководителей и просто работников других отделов. Несколько часов можно было провести за бессмысленным разговором в скайпе с парой тройкой людей, которые сами не понимают, что от тебя хотят. Указы начальства касательно будущих фич не обсуждались, даже некоторые предложения по корректировками отметаюсь на самом первом уровне.

Я проработал там 4 месяца. И это, наверное, даже долго. Без угрызений совести сменил работу.

Мораль проста - не надо себя мучать, не нравится - уходи.


Если ты не хочешь делать на будущей работе какие-то вещи не указывай их в резюме

Это частая ошибка которую я допускаю и, скорее всего, допущу снова.

Когда ты ищешь работу, очевидно, что ты хочешь сделать свое резюме как можно привлекательнее. Как? Включаешь туда все-все вещи, с которыми когда-то имел дело. Каждую технологию, каждый язык, на котором написал хотя бы ‘’hello world”. А потом всегда удивляешься, когда тебя назначают “главным” по технологии, которая тебе не нравится. 

Обычно это происходит по следующему сценарию:


“Ты указал в резюме, что имеешь большой опыт в jquery. Теперь ты будешь поддерживать весь наш jquery легаси код”.


Вот ты и застрял на позиции jquery-мэна.


Как этого избежать? Относиться к резюме не только как к списку прошлых мест работы и технологий, но как к помощнику в формировании следующего шага в карьере. Твое резюме может устанавливает границы, в пределах которых люди будут нанимать тебя. Если ты сделаешь эти границы такими широким как только можно, то ты, безусловно, получишь много предложений, но, скорее, всего, большая часть из них будет на позиции, на которых ты работать не захочешь. Сужение фокуса твоего резюме до технологий ,которые ты действительно хорошо знаешь и с которыми хотел бы работать, гарантирует, что ты не окажешься в положении, которое тебе не нравится и требует поиска нового работы.



Я что-то упустил? Есть еще какие-то вещи, которые каждый разработчик должен обязательно делать? Делитесь в https://teleg.run/progersclub

","alias":"9-things-every-developer-should-do","created":"2019-12-18 15:11:49","tags":null,"active":1,"excludeFromBlog":0},{"id":53,"title":"Что такое Test-Driven Development пример","description":"Простой пример программирования по методологии Test-Driven Development","header":"Что такое TDD? Почему я выбираю Test-Driven Development!?","content":"

Я уже немного касался этого в предыдущем посте.


Что же такого в этой Test-Driven Development? В чем особенности и преимущества этой методологии?


Разработка через тестирование(так переводится TDD на русский язык) предполагает, что сначала мы пишем тест, после реализацию. Как это? Не логично? Как можно тестировать то, чего нет? 


А очень просто. Нужно научиться начинать с конца. То есть сначала нужно понять какой результат мы ждем от конкретной части программы, записать этот результат в виде теста, а после сделать так чтобы тест проходил.

Главные принципы Test-Driven Development:

  1. Сначала пишется тест
  2. Пишется ровно столько кода, сколько нужно для того, чтобы тест прошел
  3. Если необходимо, то устраняется дублирование и производится рефакторинг
  4. Пишется новый тест, описывающий дополнительные детали поведения программы
  5. Возвращаемся к пункту №2


И так до тех пор пока реализуемая часть программы не будет работать так как задумано. 


Я надеюсь, что небольшой пример внесет ясность. (Напишу его на JavaScript)

К примеру, нам нужно реализовать функцию, которая конвертирует граммы в килограммы.

Мы ожидаем, что мы передадим в эту функцию значение в граммах, а на выходе получим значение в килограммах. 


Первый тест, который мы напишем, может выглядеть так(Я пока нарочно не использую никакие Фреймворки для тестирования, потому что хочу показать принцип):


console.log(grammsToKg(1000) === 1);


Я просто вызвал функцию и жду, он вернет мне 1, потому что 1000 граммов - это 1 килограмм.


Если запустить этот код, то будет выброшено исключение


Uncaught ReferenceError: grammsToKg is not defined


, потому что такой функции нет. Чтобы исключения не было достаточно объявить эту функцию:


function grammsToKg(value

{

}

console.log(grammsToKg(1000) === 1);


Исключения больше нет, но console.log() выводит false, потому что наша функция ничего не возвращает. Следуя методологии TDD мы должны совершить минимум действий, чтобы починить тест. Рефакторингом и доработками займемся на следующих шагах.


Что нужно чтобы тест прошел?


function grammsToKg(value)

{

    return 1;

}


console.log(grammsToKg(1000) === 1);



Теперь console.log() выводит true. Тест прошел! Ура. Мы реализовали, то что хотели? Функция работает корректно для 1000 граммов. Давайте добавим еще один тест


function grammsToKg($value)

{

    return 1;

}


console.log(grammsToKg(1000) === 1);

console.log(grammsToKg(100) === 0.1);


Ведь 100 граммов - это 0,1 кг, верно? Но второй console.log выводит false, потому что функция всегда возвращает константу. 

Нам нужно исправить реализацию функции, чтобы починить тест: 


function grammsToKg(value)

{

    return value / 1000;

}


console.log(grammsToKg(1000) === 1);

console.log(grammsToKg(100) === 0.1);



В одном килограмме 1000 граммов, значит надо передаваемое в функцию значение делить 1000.


Теперь оба наших console.log выводят true. 


Для уверенность мы можем создать некий набор для проверки работы функции на большем количестве значений:


const values = [

    { g: 1000, kg: 1 },

    { g: 100, kg: 0.1 },

    { g: 5000, kg: 5 },

    { g: 6, kg: 0.006 }

];


Это массив объектов, в каждом из которых есть поле g, которое соответсвует значению в граммах, его мы будем передавать в качестве аргумента в нашу функцию, и есть поле kg - это то, что мы ждем в качестве возвращаемого значения от функции в этом случае.

И запустим в цикле проверку:


function grammsToKg(value)

{

    return value / 1000;

}


const testData = [

    { g: 1000, kg: 1 },

    { g: 100, kg: 0.1 },

    { g: 5000, kg: 5 },

    { g: 6, kg: 0.006 }

];


testData.map(item => {

    console.log(grammsToKg(item.g) === item.kg);

});


Если запустим, то мы увидим в консоле 4 раза true.

Вот мы реализовали свою первую функцию используя подход TDD.


Мы написали ровно столько кода сколько нужно. Да, это очень простая задача для понимания порядка, в котором нужно размышлять, когда разрабатываешь через тестирование. Дальше будут задачи поинтереснее.

Я думаю, код мы будем писать на php, потому что это очень простой язык и для него есть хороший классический фреймворк для тестирования. При этом наши решение не составит труда перенести и на другие языки. Вероятно и этим мы займемся тоже при рассмотрении инструментов для разных языков.


Для начала нам нужно понять принципы #TDD.

Когда мы разрабатываем используя TDD, мы сначала предполагаем какой результат мы ждем он данной части программы и записываем это ожидание в виде теста.

Далее мы делаем минимум движений, чтобы тест прошел, затем устраняем дублирование, делаем рефакторинг, если это необходимо.

Пишем новый тест для проверки работы программы с другими условиями.

Исправляем реализацию, делаем рефакторинг и т д пока программа не будет завершена.


Что прекрасно в таком подходе, так это то, что мы точно знаем, когда мы получили тот функционал, который предполагался. Мы точно знаем, когда эта часть программы готова и не напишем ни больше ни меньше.


Из-за того, что новый код пишется после нового теста, у нас есть уверенность, что код работает правильно и учтены и задокументированных частные случаи.


В процессе мы постоянно запускаем код и имеет четкое представление как он работает.


Если что-то работает не так, то мы исправляем это здесь и сейчас, на этапе разработки. При ином подходе, при выявлении ошибки, придется вносить исправления в уже готовый код, что может занять больше времени и сил. Вероятно вообще весь код работает неправильно из-за ошибки на раннем этапе разработки, которую заметили уже после завершения написания программы.


Мы не пытаемся предсказать будущее, а сразу же приступаем к изучению проблемы на практике, пошагово реализуя необходимый функционал.


Далее будет больше примеров. Сложнее и интереснее. 


Обсудить это можно в чате - https://teleg.run/progersclub

Почитать канал в телеграм - https://teleg.run/it_programmist

","alias":"why-tdd","created":"2019-11-25 09:27:47","tags":"[\"TDD\"]","active":1,"excludeFromBlog":0},{"id":52,"title":"Для чего программисту писать тесты и нужно ли","description":"Почему полезно писать тесты плюсы и минусы","header":"Зачем программисту писать тесты?","content":"


#TDD


Зачем вообще нужно тестирование? Почему #TDD?


Первый вопрос который отделяет программиста от написания тестов - это вопрос “Зачем?”. И действительно. Ведь тесты - это лишний код и лишнее затраченное время. Зачем писать тесты, когда можно написать программу, запустить её(открыть сайт в браузере) и убедиться что все работает как надо. Это резонный вопрос, если у тебя landing page, где только html или программа, которая всего лишь складывает два числа. В таком случае тесты не нужны.


Если же у тебя мало-мальски сложная архитектура или командная разработка, то тесты нужны. Ниже я постараюсь тезисно описать плюсы и минусы написания тестов и TDD.


Кстати…

Самый любопытный читатель уже, наверное, давно загуглил, что же такое Test-Driven Development (TDD), а может и вовсе знал и без меня.

Я на определении и идеологии остановлюсь позже.

Если коротко, разработка через тестирование(именно так переводят Test-Driven Development на русский язык) предполагает, что сначала пишется тест, а только после реализация.

К примеру, мы хотим запрограммировать функцию деления двух чисел `div`. Сначала мы пишем тест, во котором проверяем что результат `div(4,2)` - это 2. Тест, разумеется сломан, потому что такой функции нет. 

Затем мы пишем эту функцию и тест чинится, а мы можем писать тесты на какие-то особые случаи, например, какой результат мы ожидаем, когда второй аргумент(делитель) равен нулю.

Подробнее мы вернемся ко всему этому позже. На данном этапе на достаточно такого общего представления. Нам же пока нужно понять “зачем”?


Плюсы написания тестов


Стабильность и легкость отладки


Это самое главное, но на начальном этапе самое незаметное. 

У тебя написаны тесты на твой функционал, ты дописываешь что-то новое и твои тесты ломаются. Это плохо? Это хорошо. Если бы у тебя не было тестов, сломалось бы оно продакшене и узнал бы ты это от пользователей.

Осталось только выяснить, это твой новый код ломает что-то старое и надо исправлять его или изменилась бизнес-логика и надо менять то, что написано раньше. В любом случае, ты знаешь, где оно сломалось и почему. И самое главное все это на этапе разработки.


Особенно ценен этот пункт для командной разработки, когда один разработчик может случайно сломать код другого, или для длительных проектов, когда просто забываешь какие-то детали. Отсюда плавно вытекает следующий пункт


Задокументированность кода


Тесты - это, своего рода, документация поведения программы для разработчиков. Тебе достаточно взглянуть на тесты, чтобы понять как работает определенная часть кода. Нет нужды компилировать этот код у себя в голове, пролистывая код реализации. По тестам ты видишь, что `эта` функция делает `это`. Это ускоряет и упрощает внесение изменений, отладку, отлавливание багов и введение новых разработчиков в проект.


Код становиться красивее


Когда ты пишешь тесты, ты пишешь код так, чтобы его можно было тестировать. Из твоего кода исчезают высокая связанность, скрытые зависимости , антипаттерны и неявные участки кода просто, потому что их невозможно тестировать. В какой-то момент при написании кода ты начинаешь думать “А как я буду это тестировать”, что избавляет тебя от спорных решений. Ты стараешься упростить код. Через некоторое время, вернувшись к этому коду, тебе не требуется много времени, чтобы понять, что здесь происходит.

А следовательно…


Инвестиции в будущее.


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

Ты уже работаешь над другой задачей, а может уже полгода прошло, или вообще это не твой код. Ты открываешь код и, какое, счастье, что код понятный, а глядя на тесты, становиться понятно что он делает. Это экономит много времени. 


Не пишешь лишнего кода.

Это относиться именно к TDD(сначала тест, потом реализация)

В работе у тебя есть конкретная задача. Ты пишешь тест, который должен эту задачу выполнять. Ты пишешь реализацию, которая чинит этот тест. Всё. 

Когда ты пишешь сразу функционал, то есть вероятность, что ты напишешь что-то “впрок”, может потом и пригодится, а может и нет!

Лучше написать меньше, потратить на этот код меньше времени, реализация будет чище и понятнее. Если в будущем появятся дополнительные требования, которые ты не учел сейчас, то тогда напишешь еще тест и исправишь реализацию. Всему свое время. 


Ну, а минусы есть?


Можно попробовать найти и привести контраргументы


Время уходит на написание тестов, а не на программирование. Вместо того, чтобы реализовать следующие задачи, пишешь тесты.


Это, наверное, самый популярный и кажущийся очевидным аргумент. 


Что ж…


Во-первых, на написание самого теста уходит не так уж и много времени. Даже мало. Чаще всего это несколько строчек с вызовом функции и сравнением результата ожидаемым поведением.

Во-вторых, ты не тратишь времени, чтобы увидеть промежуточные результаты в браузере/консоли. Тест - это индикатор того, что код работает как и задумано.

В-третьих, раз уж мы заговорил о потраченном времени, тесты, как я писал выше, это инвестиция в будущее. Гораздо меньше времени уйдет в будущем на внесение изменений, поиск бага или введение в курс дела нового сотрудника.


Лишний код.


Написание тестов влечет за собой создание дополнительных файлов и увеличения кодовой базы.

На самом деле, проекты с тестами хорошо структурированы. Тесты лежат в отдельной директории с говорящим названием(например tests). В этой директории повторяется структура папок проекта, следовательно найти тесты к определенной части функционала довольно просто. 

Да, файлов и, соответсвенно, кода больше, однако тесты лежат отдельно и служат своего рода документацией работы определенной части программы. Мы в любой момент можем открыть тест и посмотреть, что делает конкретная часть программы, вместо того, чтобы интерпретировать код этой части в голове или вставлять в код дебажные console.log или print_r


Итак, написание тестов это хорошо потому что:



В следующий раз поговорим уже наконец-то о Test-Driven Development

","alias":"why-tests","created":"2019-11-12 12:47:12","tags":"[\"TDD\"]","active":1,"excludeFromBlog":0},{"id":47,"title":"MySQL или MongoDB?","description":"Долгое время использовал лишь реляционные базы данных, но когда познакомился с nodejs, стал всё чаще использовать mongodb. Сегодня попробуем сравнить и выяснить когда какое решение подходит лучше.Почему в данном случае...","header":"MySQL или MongoDB?","content":"

Долгое время использовал лишь реляционные базы данных, но когда познакомился с nodejs, стал всё чаще использовать mongodb. Сегодня попробуем сравнить и выяснить когда какое решение подходит лучше.

Почему в данном случае мы сравниваем именно MySQL и MongoDB, не другие виды реляционных и NoSQL БД? На самом деле причин несколько. Если посмотреть на всевозможные рейтинги баз данных, то мы видим, что MySQL, согласно этому рейтингу, — наиболее популярная реляционная база данных, а MongoDB — наиболее популярная нереляционная база данных. Поэтому их разумно сравнивать.

А ещё у меня есть наибольший опыт в использовании этих двух баз данных, а я привык писать о том, о чём знаю.

И так начнём выбор по пунктам.

Опыт и предпочтения команды

Что наиболее важно на мой взгляд — это учитывать, какие есть опыт и предпочтения команды. Для многих задач подходят оба решения. Их можно сделать и так, и так, может быть несколько сложнее, может быть несколько проще. Но если у вас команда, которая долго работала с SQL-базами данных и понимает реляционную алгебру и прочее, может быть сложно перетягивать и заставлять их использовать нереляционные базы данных, такие как MongoDB, где нет даже полноценной транзакции.

И наоборот: если есть какая-то команда, которая использует и хорошо знает MongoDB, SQL-язык может быть для неё сложен. Также имеет смысл рассматривать как оригинальную разработку, так и дальнейшее сопровождение и администрирование, поскольку всё это в итоге важно в цикле приложения.

Преимущества

Если говорить про MySQL — это проверенная технология. Понятно, что MySQL используется крупными компаниями более 15 лет. Так как он использует стандарт SQL, есть возможность достаточно простой миграции на другие SQL-базы данных, если захочется. Есть возможность транзакций. Поддерживаются сложные запросы, включая аналитику. И так далее.

С точки зрения MongoDB, здесь преимущество то, что у нас гибкий JSON-формат документов. Для некоторых задач и каким-то разработчикам это удобнее, чем мучиться с добавлением колонок в SQL-базах данных. Не нужно учить SQL — для некоторых это сложно. Простые запросы реже создают проблемы. Если посмотреть на проблемы производительности, в основном они возникают, когда люди пишут сложные запросы с JOIN в кучу таблиц и GROUP BY. Если такой функциональности в системе нет, то создать сложный запрос получается сложнее.

В MongoDB встроена достаточно простая масштабируемость с использованием технологии шардинга. Сложные запросы если возникают, мы их обычно решаем на стороне приложения. То есть, если нам нужно сделать что-то вроде JOIN, мы можем сходить выбрать данные, потом сходить выбрать данные по ссылкам и затем их обработать на стороне приложения. Для людей, которые знают язык SQL, это выглядит как-то убого и ненатурально. Но на самом деле для многих разработка application-серверов такое куда проще, чем разбираться с JOIN.

Подход к разработке

Если говорить про приложения, где используется MongoDB, и на чём они фокусируются — это очень быстрая разработка. Потому что всё можно постоянно менять, не нужно постоянно заботиться о строгом формате документа.

Второй момент — это схема данных. Здесь нужно понимать, что у данных всегда есть схема, вопрос лишь в том, где она реализуется. Ты можешь реализовывать схему данных у себя в приложении, потому что каким-то же образом ты эти данные используешь. Либо эта схема реализуется на уровне базы данных.

Очень часто если у вас есть какое-то приложение, с данными в базе данных работает только это приложение. Например, мы сохраняем данные из этого приложения в эту базу данных. Схема на уровне приложения работает хорошо. Если у нас одни и те же данные используются многими приложениями, то это очень неудобно, сложно контролировать.

У MongoDB структура данных основана на документах. Данные многих веб-приложений отображать очень просто. Потому что если храним структуру — что-то вроде ассоциированного массива приложения, то это очень просто и понятно для разработчика сериализуется в JSON-документ. Раскладывать такое в реляционной базе данных по разным табличкам — задача более нетривиальная.

Производительность

Производительность очень сложно сравнивать напрямую, потому что мы часто делаем разные схемы баз данных, дизайн приложения. Но если говорить в целом, MongoDB изначально была сделана, чтобы хорошо масштабироваться на много узлов, поэтому эффективности было уделено меньше внимания.

Масштабируемость.

Что такое масштабируемость в данном контексте? То, насколько легко нам взять наше маленькое приложение и масштабировать его на многие миллионы, может быть, даже на миллиарды пользователей.

В MySQL в новых версиях весьма хорошая масштабируемость в рамках одного узла для LTP-нагрузок. Если у нас маленькие транзакции, есть какое-нибудь железо, в котором 64 процессора, то масштабируется достаточно хорошо. Аналитика или сложные запросы масштабируются плохо, потому что MySQL может использовать для одного запроса только один поток, что плохо.

В MongoDB фокус изначально был в масштабируемости на многих узлах. Даже в случаях с маленьким приложением многим рекомендуется использовать шардинг с самого начала. Может, всего пару replica set, потом вы будете расти вместе со своим приложением. 

__________

Каждому своё, как говорится

","alias":"mysql-ili-mongodb","created":"2018-03-20 06:29:57","tags":null,"active":1,"excludeFromBlog":0},{"id":46,"title":"Пример unit-тестирования на php","description":"Мы уже познакомились с тестированием. Узнали о видах и даже разобрались что к чему? Но это всё была теория. Без примеров всё это не то.Предыдущие статьи:https://mynrg.ru/zachem-nuzhno-testirovaniehttps://mynrg.ru/testirovanie-vidyЧасто привожу и буду приводить...","header":"Пример unit-тестирования на php","content":"

Мы уже познакомились с тестированием. Узнали о видах и даже разобрались что к чему? Но это всё была теория. Без примеров всё это не то.

Предыдущие статьи:

https://mynrg.ru/zachem-nuzhno-testirovanie

https://mynrg.ru/testirovanie-vidy

Часто привожу и буду приводить примеры на php, потому что язык прост и понятен. И поняв что-то на php, перенести это на другой язык не составит труда.

Я уже касался TDD - Test Driven Development.

Что такое Test Driven Development?

Идея Test Driven Development в том, что ты пишешь код таким образом, что сначала пишешь еще одну часть кода, единственная цель которого заключается в том, чтобы убедиться, что изначально предназначенный код работает, даже если он еще не написан.

Что такое PHPUnit?

PHPUnit - это фреймворк для тестирования на php.

Это набор утилит (классы PHP и исполняемые файлы), который не только упрощает сложный процесс создания тестов (частое написание тестов влечет за собой написание большего количества кода, чем на самом деле оно того стоит), но также позволяет видеть процесс тестирования в наглядном виде и позволяет узнать

Чтобы не утомлять слишком большим текстом (или уже слишком поздно?), давай попробуем использовать это фреймворк и начнём учиться на примерах.

Каркас приложения

Поисковики выдают кучу однотипных статей для новичков на тему unit-тестирования, где тестируются функции умножения, возведения в степень и т.п.

Да там понятна суть тестирования, синтаксис и инструменты, но в таком виде тяжело понять зачем оно надо. Хотя у меня и есть на эту тему целая статья.

Для того чтобы воссоздать структуру реального php-приложения возьмём https://github.com/php-pds/skeleton.

Пойдём в папку с нашими проектами и склонируем этот каркас:

git clone https://github.com/php-pds/skeleton converter
cd converter
composer require phpunit/phpunit --dev

Обрати внимание, что мы использовали --dev флаг только для установки PHPUnit в качестве зависимости от разработчика, то есть он не нужен в продакшене, что облегчит проект. Также обрати внимание, что использование с PDS-Skeleton означает, что наша папка tests уже создана для нас, с двумя демонстрационными файлами, которые мы удалим.

Далее, нам нужен фронт-контроллер для нашего приложения - файл, через который проходят все запросы и маршрутизируются. В папку converter/public, создаю index.phpсо следующим содержимым:

<?php
echo "Hello world";

Если ты повторяешь все эти действия, то я предполагаю, что ты знаешь php, ООП, composer и как установить и запустить php(хотя бы у себя на компе, хотя бы используя denwer).

Открыв проект в браузере, мы должны увидеть

Нужно удалить лишние файлы. Это можно сделать либо руками, либо из командной строки:

rm -rf bin/* src/* docs/* tests/*

Нам нужен файл конфигурации PHPUnit, который сообщает PHPUnit, где искать тесты, какие шаги подготовки необходимо выполнить перед тестированием и как тестировать. В корне проекта создаю файл phpunit.xmlсо следующим содержимым:

<phpunit bootstrap="tests/autoload.php">
 <testsuites>
 <testsuite name="converter">
  <directory suffix="Test.php">tests</directory>
 </testsuite>
 </testsuites>
</phpunit>

Проект может иметь несколько наборов тестов, в зависимости от контекста. Например, все связанные с учетной записью пользователя могут быть сгруппированы в набор под названием «users» и может иметь свои собственные правила или другую папку для тестирования этого функционала. В нашем случае проект очень мал, поэтому одной группы тестов более чем достаточно, ни будут в папке tests, что мы и говорим тэгом

<directory suffix="Test.php">tests</directory>

В phpunit.xmlмы определили аргумент для тэга directory - suffix - это означает, что PHPUnit будет запускать только те файлы, которые заканчиваются Test.php. Полезно, когда мы хотим использовать и другие файлы из папки tests, но не хотим, чтобы они запускались автоматически. Например вспомогательные файлы для тестов.

Прочитать о других аргументах для конфигурации можно здесь .

Значение аругмента bootstrap указывает PHPUnit , какой файл PHP загрузить перед тестированием. Это полезно при настройке параметров автоматической загрузки или загрузки тестовых переменных, даже тестовой базы данных и т. д. То есть убрать, то что не нужно для тестов, а нужно только на продакшене или добавить то, что не нужно на продакшене, но нужно здесь.

Давайте создадим tests/autoload.php:

<?php
require_once __DIR__.'/../vendor/autoload.php';

В этом случае мы просто загружаем автозагрузчик по умолчанию Composer, потому что у PDS-Skeleton уже есть пространство имен Tests, настроенное для нас composer.json. Если мы заменим значения шаблона в этом файле собственными, в итоге получим composer.jsonследующее:

{
\"name\": \"mynrg.ru/jsonconverter\",
\"type\": \"standard\",
\"description\": \"A converter from JSON files to PHP array files.\",
\"homepage\": \"https://github.com/php-pds/skeleton\",
\"license\": \"MIT\",
\"autoload\": {
\"psr-4\": {
\"MyNrg\\\\\": \"src/MyNrg\"
}
},
\"autoload-dev\": {
\"psr-4\": {
\"MyNrg\\\\\": \"tests/MyNrg\"
}
},
\"bin\": [\"bin/converter\"],
\"require-dev\": {
\"phpunit/phpunit\": \"^6.2\"
}
}

После этого мы запускаем composer du(сокращенно dump-autoload) для обновления сценариев автозагрузки.

composer du

Первый тест

Помните, что TDD - это искусство сначала делать ошибки, а затем вносить изменения в код, который заставляет их перестать быть ошибками, а не наоборот. Имея это в виду, давайте создадим наш первый тест.

tests/MyNrg/Converter/ConverterTest.php

<?php

namespace MyNrg\\Converter;

use PHPUnit\\Framework\\TestCase;

class ConverterTest extends TestCase {

 public function testHello() {
  $this->assertEquals('Hello', 'Hell' . 'o');
 }

}

Лучше всего, если тесты будут следовать той же структуре, которую мы закладываем в проект. Имея это в виду, мы даем им те же пространства имен и располагаем также в дереве папок. Таким образом, наш ConverterTest.phpфайл находится в tests в подпапке MyNrg, вложенной папке Converter.

В этом примере «тестовый пример» утверждается, что строка Hello равна конкатенации Hell и o

Если  выполнить:

php vendor/bin/phpunit

запустится тест и мы получим положительный результат.

PHPUnit запускает каждый метод, который начинается с testв файле с тестами, если не указано иное. 

Но этот тест не является ни полезным, ни реалистичным. Мы использовали его только для проверки работы нашей установки. Давайте сейчас напишем правильный. Перепишем ConverterTest.phpфайл следующим образом:

<?php

namespace MyNrg\\Converter;
use PHPUnit\\Framework\\TestCase;

class ConverterTest extends TestCase
{

 public function testSimpleConversion()
 {
  $input = '{"key":"value","key2":"value2"}';
  $output = [
   'key' => 'value',
   'key2' => 'value2'
  ];
  $converter = new \\MyNrg\\Converter\\Converter();
  $this->assertEquals($output, $converter->convertString($input));
 }
}
}

Мы тестируем простое преобразование(testSimpleConversion). Входные данные $input представляют собой JSON объект, который приведён к типу строки (JSON.stringify в JS), а ожидаемый вывод($output) - это массив PHP.  Наш тест утверждает , что наш класс конвертер, при обработке с $inputиспользованием convertStringметода, дает желаемое $output, так же , как это определено здесь.

Если мы сейчас запустим тест, то, конечно же, будет ошибка.

 Естественно, класс же еще не существует.

Отредактируем phpunit.xml файл, добавив к phpunitатрибут colors=\"true\":

<phpunit colors="true" bootstrap="tests/autoload.php">

Теперь, если мы запустим php vendor/bin/phpunit, мы получим такой результат:

Создание тестового прохода

Теперь мы начинаем процесс прохождения этого теста.

Наша первая ошибка: Класс MyNrg\\Converter\\ Converter не найден. Давайте это исправим.

Создадим src/MyNrg/Converter/Converter.php:

<?php

namespace MyNrg\\Converter;

class Converter
{

}

Теперь, если мы перезапустим тест

Уже лучше! Нам не хватает метода, который мы вызываем в тесте. Давайте добавим его в наш класс.

<?php

namespace MyNrg\\Converter;

class Converter
{
  public function convertString(string $input): ?array
  {

  }
}

Мы определили метод, который принимает параметр строкового типа, и возвращает либо массив, либо null, если что-то пошло не так. Если ты не знаком со типизацией в php типами ( string $input), узнай больше здесь и для понимания возвращаемых значений типа null ( ?array), см. здесь .

Перезапустим тест

Это ошибка возвращаемго типа - функция ничего не возвращает (void) - потому что она пуста , а ожидается, что она вернет либо null, либо массив. Давайте завершим метод. Мы будем использовать встроенную PHP json_decodeфункцию для декодирования строки JSON.

public function convertString(string $input): ?array
{
$output = json_decode($input);
return $output;
}

Ну, вроде, всё должно работать, разве нет?

Функция возвращает объект, а не массив. Ах, ха! Это потому, что мы не активировали режим «ассоциативного массива» для функции json_decode.

Функция превращает объект JSON в экземпляр класса stdClassпо умолчанию, если не указано иное. Изменим это так:

public function convertString(string $input): ?array
{
$output = json_decode($input, true);
return $output;
}

Наш тест теперь проходит! Он получает тот же результат, который мы ожидаем от него в тесте!

Давайте добавим еще несколько тестовых примеров, чтобы убедиться, что наш метод действительно работает по назначению. Давайте сделаем это немного сложнее, чем простой пример, с которого мы начали. Добавьте следующие методы ConverterTest.php:

{
$input = '{\"key\":\"value\",\"key2\":\"value2\",\"some-array\":[1,2,3,4,5]}';
$output = [
'key' => 'value',
'key2' => 'value2',
'some-array' => [1, 2, 3, 4, 5],
];
$converter = new \\MyNrg\\Converter\\Converter();
$this->assertEquals($output, $converter->convertString($input));
}

public function testMoreComplexConversion()
{
$input = '{\"key\":\"value\",\"key2\":\"value2\",\"some-array\":[1,2,3,4,5],\"new-object\":{\"key\":\"value\",\"key2\":\"value2\"}}';
$output = [
'key' => 'value',
'key2' => 'value2',
'some-array' => [1, 2, 3, 4, 5],
'new-object' => [
'key' => 'value',
'key2' => 'value2',
],
];
$converter = new \\MyNrg\\Converter\\Converter();
$this->assertEquals($output, $converter->convertString($input));
}

public function testMostComplexConversion()
{
$input = '[{\"key\":\"value\",\"key2\":\"value2\",\"some-array\":[1,2,3,4,5],\"new-object\":{\"key\":\"value\",\"key2\":\"value2\"}},{\"key\":\"value\",\"key2\":\"value2\",\"some-array\":[1,2,3,4,5],\"new-object\":{\"key\":\"value\",\"key2\":\"value2\"}},{\"key\":\"value\",\"key2\":\"value2\",\"some-array\":[1,2,3,4,5],\"new-object\":{\"key\":\"value\",\"key2\":\"value2\"}}]';
$output = [
[
'key' => 'value',
'key2' => 'value2',
'some-array' => [1, 2, 3, 4, 5],
'new-object' => [
'key' => 'value',
'key2' => 'value2',
],
],
[
'key' => 'value',
'key2' => 'value2',
'some-array' => [1, 2, 3, 4, 5],
'new-object' => [
'key' => 'value',
'key2' => 'value2',
],
],
[
'key' => 'value',
'key2' => 'value2',
'some-array' => [1, 2, 3, 4, 5],
'new-object' => [
'key' => 'value',
'key2' => 'value2',
],
],
];
$converter = new \\MyNrg\\Converter\\Converter();
$this->assertEquals($output, $converter->convertString($input));
}

Мы сделали каждый тестовый пример немного более сложным, чем предыдущий. Повторный запуск тестового набора показывает нам, что все в порядке ...

 но что-то не так, не так ли? Здесь очень много повторений, и если мы когда-либо изменим API класса, нам придется внести изменения в 4 местоположения (и это только пока). 

Data Providers

Поставщики данных - это специальные функции в тестовых классах, которые имеют одну конкретную цель: предоставить набор данных тестовой функции, так что вам не нужно будет повторять свою логику в нескольких тестовых функциях, как это было сделано. Это лучше всего объяснить на примере. Давайте переформулируем наш ConverterTestкласс следующим образом:

<?php

namespace MyNrg\\Converter;

use PHPUnit\\Framework\\TestCase;

class ConverterTest extends TestCase
{

 public function conversionSuccessfulProvider()
 {
  return [
   [
    '{"key":"value","key2":"value2"}',
    [
     'key' => 'value',
     'key2' => 'value2',
    ],
   ],

   [
    '{"key":"value","key2":"value2","some-array":[1,2,3,4,5]}',
    [
     'key'  => 'value',
     'key2'  => 'value2',
     'some-array' => [1, 2, 3, 4, 5],
    ],
   ],

   [
    '{"key":"value","key2":"value2","some-array":[1,2,3,4,5],"new-object":{"key":"value","key2":"value2"}}',
    [
     'key'  => 'value',
     'key2'  => 'value2',
     'some-array' => [1, 2, 3, 4, 5],
     'new-object' => [
      'key' => 'value',
      'key2' => 'value2',
     ],
    ],
   ],

   [
    '[{"key":"value","key2":"value2","some-array":[1,2,3,4,5],"new-object":{"key":"value","key2":"value2"}},{"key":"value","key2":"value2","some-array":[1,2,3,4,5],"new-object":{"key":"value","key2":"value2"}},{"key":"value","key2":"value2","some-array":[1,2,3,4,5],"new-object":{"key":"value","key2":"value2"}}]',
    [
     [
      'key'  => 'value',
      'key2'  => 'value2',
      'some-array' => [1, 2, 3, 4, 5],
      'new-object' => [
       'key' => 'value',
       'key2' => 'value2',
      ],
     ],
     [
      'key'  => 'value',
      'key2'  => 'value2',
      'some-array' => [1, 2, 3, 4, 5],
      'new-object' => [
       'key' => 'value',
       'key2' => 'value2',
      ],
     ],
     [
      'key'  => 'value',
      'key2'  => 'value2',
      'some-array' => [1, 2, 3, 4, 5],
      'new-object' => [
       'key' => 'value',
       'key2' => 'value2',
      ],
     ],
    ],
   ],

  ];
 }

 /**
  * @param $input
  * @param $output
  * @dataProvider conversionSuccessfulProvider
  */
 public function testStringConversionSuccess($input, $output)
 {
  $converter = new \\MyNrg\\Converter\\Converter();
  $this->assertEquals($output, $converter->convertString($input));
 }

}

Сначала мы написали новый метод conversionSuccessfulProvider. Это указывает на то, что все предоставленные случаи должны возвращать положительный результат, потому что вывод и ввод совпадают. Поставщики данных возвращают массивы (чтобы тестовая функция могла автоматически перебирать элементы). Один элемент массива - это один тестовый пример - в нашем случае каждый элемент представляет собой массив с двумя элементами: первый входные данные, второй - вывод в виде массива.

Затем мы собрали функции тестирования в одну с более общим названием, что свидетельствует о том, что ожидается: testStringConversionSuccess. Этот метод тестирования допускает два аргумента: ввод и вывод. Остальная логика идентична тому, что было раньше. Кроме того, чтобы убедиться, что метод использует dataprovider, мы объявляем dataProvider с помощью @dataProvider conversionSuccessfulProvider.

Вот и все. И, запустив, мы получаем тот же результат.

Если мы хотим добавить дополнительные тестовые примеры, нам нужно только добавить дополнительные пары ввода-вывода в провайдер. Не нужно изобретать новые имена методов или повторять код.

Введение в покрытие кода

Покрытие кода - это показатель, указывающий, сколько из нашего кода покрывается тестами. Если наш класс имеет два метода, но только один из них проверяется в тестах, то наш охват кода составляет не более 50% - в зависимости от того, сколько логических веток (IF, switch, циклов и т. д.) имеют методы (каждая ветка должна быть охвачена отдельным тестом). PHPUnit имеет возможность автоматически генерировать отчеты о покрытии кода после запуска данного набора тестов.

Давайте быстро это настроим. Мы будем расширять phpunit.xml, добавляя тэги и  как элементы сразу внутри , так что они получаются элементами первого уровня (если принять, что это коневой элемент и его уровень 0):

<phpunit ...>
 <filter>
  <whitelist>
   <directory suffix=".php">src/</directory>
  </whitelist>
 </filter>
 <logging>
  <log type="tap" target="tests/build/report.tap"/>
  <log type="junit" target="tests/build/report.junit.xml"/>
  <log type="coverage-html" target="tests/build/coverage" charset="UTF-8" yui="true" highlight="true"/>
  <log type="coverage-text" target="tests/build/coverage.txt"/>
  <log type="coverage-clover" target="tests/build/logs/clover.xml"/>
 </logging>

Тэг filter настраивает белый список, указывающий PHPUnit, на какие файлы обратить внимание при тестировании. У нас будут проверяться все .php-файлы внутри папки /src на любом уровне .

Logging  сообщает PHPUnit какие отчёт(логи) генерировать - различные инструменты могут читать различные логи, поэтому ничего страшного, что мы создаём больше форматов, чем может потребоваться. В нашем случае нас действительно интересует только HTML.

Прежде чем это сработает, нам нужно активировать XDebug. PHPUnit использует XDebug для проверки классов, которые он проходит.

Повторный запуск теста теперь будет информировать нас о сгенерированных отчетах о покрытии. Кроме того, они появятся в дереве каталогов в указанном месте.

Давайте откроем index.htmlфайл в браузере. 

В индексном файле будет отображаться сводка всех тестов.Можно щелкнуть по отдельным классам, чтобы просмотреть их подробные отчеты о покрытии, а наведение на тела методов вызовет подсказки, которые объясняют, насколько данный метод проверен.

Мы поняли принцип TDD и внедрили тесты на простейшем php-приложении.

","alias":"primer-unit-testirovaniya-na-php","created":"2018-03-13 12:12:16","tags":null,"active":1,"excludeFromBlog":0}],"loading":false,"error":null},"application":{"meta":{"title":"Интересные особенности HTML, которые ты, возможно, и не знал","description":"Что нового может быть в HTML? Казалось, что всё уже написано, рассказано и перерассказано. Но часто наблюдаю, как коллеги удивляются, казалось бы, простым общеизвестным вещам так, как будто это какой-то..."},"messages":[]},"user":{"loading":false,"error":null}}