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, не знают и половины из этого списка.


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

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

© 2019 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":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},{"id":45,"title":"Что такое TDD Test Driven Development?","description":"Разработка через тестирование - техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и...","header":"Что такое TDD Test Driven Development?","content":"

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

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

Проверка того, действительно ли то, что мы ожидаем, называется asserting(утверждением) в мире TDD. Помни этот термин.

Например, утверждение(asserting), что 2 + 2 = 4 является правильным. Но если мы утверждаем, что 2 + 3 = 4, структура тестирования будет отмечать это утверждение как ложное. Это называется «неудачным тестом»(failed test). Мы протестировали 2 + 3 = 4, и тестирование закончилось ошибкой. Очевидно, что в своём приложении ты не будешь тестировать суммы скалярных значений - вместо этого будут переменные, которые язык заменит реальными значениями во время выполнения и утвердит это, но ты главное, чтобы ты понял идею.

","alias":"chto-takoe-tdd-test-driven-development","created":"2018-03-13 10:06:28","tags":null,"active":1,"excludeFromBlog":0},{"id":44,"title":"Нужен ли английский язык программисту","description":"Часто возникает такой вопрос у начинающий программистов. В некоторых вакансиях указывают в требованиях знание английского языка, но не совсем понятно есть необходимость его учить.Ответ на вопрос нужен ли учить английский...","header":"Нужен ли английский язык программисту","content":"

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

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

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

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

if(условие){
//если условие выполняется, то выполняется код в этих скобках
}else{
//иначе в этих
}

Всё просто и логично.

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

С таким утверждением я абсолютно не согласен. Мне довелось работать с очень сильным программистом(и сисадмином заодно), который, например, this произносил как \"тхис\" и это никак не мешало ему хорошо работать.

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

Поэтому учи, лишним не будет.

","alias":"nuzhen-li-anglijskij-yazyk-programmistu","created":"2018-03-05 11:50:10","tags":null,"active":1,"excludeFromBlog":0},{"id":50,"title":"Зачем нужно тестирование?","description":"Без тестирования современную серьёзную разработку представить сложно. И на серьёзную работу(не junior ) без умений писать тесты устроиться тяжело.В первой части я писал о видах тестирования https://mynrg.ru/testirovanie-vidy.В этой статье поговорим...","header":"Зачем нужно тестирование?","content":"

Без тестирования современную серьёзную разработку представить сложно. И на серьёзную работу(не junior ) без умений писать тесты устроиться тяжело.

В первой части я писал о видах тестирования https://mynrg.ru/testirovanie-vidy.

В этой статье поговорим о том, зачем это надо.

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

Можно объяснить ещё проще.

Мы пишем функцию умножения:

function multiplication($a, $b){
/*....*/
return $result;
}

Вызываем

multiplication(2,2)

а оно возвращает 5. Мы исправляем, пока не будет 4.

Это, по сути, ручное тестирование. Но в процессе такого тестирования, мы рискуем недотестировать.

Например, вызываем мы

multiplication(3, 2)

А оно возвращает 7. Исправляем, проверяем, всё хорошо, ответ 6. Мы довольные пошли отмечать это событие, а в другом месте программы

multiplication(2,2)

стало возвращать 5.

Поэтому куда эффективнее использовать автоматическое тестирование.

Алгоритм тестирования

Как правило, поток разработки таков:

  1. Пишется спецификация теста, которая описывает самый базовый функционал.
  2. Делается начальная реализация функции.
  3. Для проверки соответствия спецификации мы задействуем фреймворк. Фреймворк запускает все тесты и выводит ошибки, если они возникнут. При ошибках вносятся исправления.
  4. Спецификация расширяется, в неё добавляются возможности, которые пока, возможно, не поддерживаются реализацией.
  5. Идём на пункт 2, делаем реализацию. И так «до победного конца».

Разработка ведётся итеративно: один проход за другим, пока спецификация и реализация не будут завершены.

Используй фреймворк.

Будет большой соблазн написать свой велосипед, но за тебя уже всё написали - бери и пользуйся.

-------------

В следующий раз, будем рассматривать тесты на примере.

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