All streams
Search
Write a publication
Pull to refresh
29
0
Пётр Грибанов @ghost404

Symfony professional developer

Send message
Это тоже ещё одна проблема, что тесты не гарантируют 100% надёжности, поскольку тестируются какие-то отдельные случаи, но не все возможные.

В таком случае интеграционные тесты имеют чуть больший процент покрытия чем модульные, но все так же далеки от 100%.
Этот вопрос разбирался в статье: «Согласно TDD, тесты предназначены для проверки функционала (feature), а не путей исполнения программы.». Не считайте пути исполнения программы (или ветки развития, как вы их называете), считайте сколько тестов вам нужно для тестирования конкретного функционала.

Хорошо. Тогда объясните что вы имеете в виду под словом функционал (feature)? Тестирование только особенностей тестируемого метода, без привязки к зависимостям? Если да, то тестирование получается не полным как и в случае с модульным. И мы опять возвращаемся к проблеме с Петей и John, ибо неполный тест может не отлавливать ситуации при которых Петя == John.

Что-то я ничего не понял. По-вашему, тесты к тестированию не имеют отношения? Что вы вообще хотите спросить\сказать?
Если что, речь шла про тесты в рамках TDD.

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

Если повсеместно использовать DI то этой проблемы не будет и не будет разницы какие тесты вы пишете, интеграционные или модульные. Это организация процесса разработки и архитектуры приложения, ну и обучения джунов. Этот вопрос не имеет прямого отношения к тестированию и TDD.
Есть у вас в программе Петя и John, и функция isEqual, которая их различает. И вот в один прекрасный день вы изменяете isEqual и тест на неё, и теперь она их не различает. А ещё есть тест на то, что жена Пети не пускает в три часа ночи незнакомых пьяных мужчин. И вот после изменения isEqual этот тест краснеет. Вы ругаете интеграционное тестирование, и удаляете этот тест, ведь «сделанные изменения ничего не ломают и ни как не влияют на корректность результата». Примерно через год после этого приходит Петя и слёзно спрашивает вас, почему его жена пускает по ночам пьяных незнакомых мужчин, и почему в его семье родился негр, хотя ни у него в роду, ни у его жены негров отродясь не было.

Об этом я говорил ниже. Интеграционные тесты позволяют тестировать контекст в котором вызывается тестируемая функция, но это не всегда нужно. Например у нас есть функция А которая проверяет какое-то условие и есть функция Б которая использует ее и с ее помощью определяет нужно ли запустить подпрограмму С. И вот мы решили изменить условия в функции А. Мы все также знаем чту функция А может возвращать true|false в разных условиях, это мы проверили через тесты. Это значит что функция Б все также будет запускать подпрограмму С, но уже в других ситуациях и это нормально. Так и должно быть.

Можно написать интеграционный тест который будет гарантировать мне что в определенных условиях функция А вернет true и функция Б соответственно запустит подпрограмму С. То есть через интеграционные тесты можно задать жесткое поведение программы. Описать все варианты развития сценария. Шаг в право, шаг в лево — расстрел. Любое изменение в коде означает изменение десятка, а то и сотни тестов.
С модульными тестами у нас больше свободы, писать их быстрее и проще, тестов меньше и выполняются они быстрее, поддерживать их в зеленом состоянии проще и меньше затрат ресурсов.

Я не спорю, интеграционные тесты это хорошо и они нужны, но они требуют столько ресурсов что выгода может не окупится. Я не говорю что их не надо писать, я говорю что нужно начать с малого, с модульных тестов. И нужно оценивать свои ресурсы. Многие компании не готовы выделить время и деньги на написание тестов вместо того что бы писать новый функционал и приносить в компании больше денег. Есть такие которые готовы потерять N $ например на уязвимости в проекте и заработать на новом функционале N^3 $.

Если DI и IOC не имеют никакого отношения к проектированию, то я — испанский лётчик.

к тестированию не имеют отношения, к тестированию

Код, реализующий функционал А, использует функционал Б. Код, реализующий функционал Б, использует функционал В. Соответственно вы можете написать 6 модульных тестов на А и Б, по 3 на штуку, и точно также можете вместо них написать 6 интеграционных тестов на А и Б.

да, только здесь умножение, а не сложение:

  • Функция А имеет 3 ветки развития алгоритма.
  • Функция А использует функцию Б.
  • Функция Б имеет 3 ветки развития алгоритма.
  • Соответственно для каждой ветки развития в функции А есть по 3 ветки развития из функции Б.
  • 3 * 3
  • Функция Б использует функцию В.
  • Функция В имеет 3 ветки развития алгоритма.
  • Соответственно для каждой ветки развития в функции Б есть по 3 ветки развития из функции В.
  • 3 * 3
  • Соответственно для каждой ветки развития в функции А есть по 3 ветки развития из функции Б и для каждой из них есть по 3 ветки развития из функции В.
  • 3 * 3 * 3

Итог: 3 * 3 * 3 = 27 веток развития алгоритма и как результат 27 вариантов результат и как результат 27 тестов функции А.

Естественно это идеализированный пример ибо не в каждой ветке развития алгоритма будет использоваться вложенная функция, но общая мысль должна быть понятна.
А если серьезно по теме. Я не против интеграционного тестирования, хотя на мой взгляд интеграционное тестирование имеет больше минусов чем плюсов по сравнению с модульным. Недавно обсуждались проблемы black-box testing и преимущества white-box testing.

Высказывание 1. Интеграционные тесты в меньшей степени помогают в поиске ошибок, нежели юнит-тесты

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

Из ваших же примеров. Добавление колонки в результат означает изменение теста измененного метода, изменение методов которые используют измененный метод, изменение тестов которые используют измененный метод т.д. В не зависимости от метода тестирования придется менять все. В случае с интеграционными тестами тесты которые нужно менять сразу покраснеют. В случае с юнит-тестами эти тесты придется искать через поиск, но это не проблема (Ctrl+F и название метода/класса), а поскольку тестов в случае юнит-тестирования в разы меньше то и результатов в поиске будет совсем чуть-чуть и тесты будут исправлены быстрее чем в случае интеграционного тестирования.

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

Высказывание 2. Интеграционные тесты в меньшей степени помогают в проектировании, нежели модульные

Это вообще не относится к проектированию и тестированию. Просто везде используется DI и все. А как вы будите тестировать класс с зависимостями это уже другая история.

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

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

Честно говоря я не понял как при интеграционном тестировании мы не столкнемся с экспоненциальным увеличением количества тестов. Ведь мы же используем реальные зависимости, а не моки, соответственно мы должны протестировать не только функциональность тестируемого метода, но и функциональность используемых зависимостей (в этом же весь смысл), а наши зависимости могут иметь свои зависимости, которые тоже могут иметь зависимости и т.д. Вот уже и получаем экспоненциальное увеличение. Это известная проблема black-box testing.

Простая математика:
  • для тестирования метода нужно написать 5 тестов
  • метод имеет 2 зависимости и для тестирования каждой из них нужно еще по 5 тестов
  • первая из зависимостей имеет тоже 2 зависимости и для тестирования каждой из них нужно еще 5

Итог: 5 * 5 * 5 * 5 * 5 = 3125 тестов для того что бы покрыть 1 единственный метод против 5 тестов в случае юнит-тестирования. 3125 тестов Карл.
И это еще простой пример с малым количеством зависимостей. Подсчитал тут интереса ради для одного своего реального метода и получил примерно 15504 теста против 57 юнит-теста.

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

Мой вывод: Единственный плюс который дает интеграционное тестирование это тестирование работы методов в контексте их использования. Это безусловно очень важный плюс, который не получит при использовании модульного тестирования, но я бы не стал зацикливаться на интеграционном тестировании только из-за него ибо для меня минусы перевешивают этот плюс.
Ну да. Понятно что в данном случае мы можем мокнуть наши сервисы, написать юнит-тест и применить test-first. Мне было интересно как применять black-box тестирование в данном случае. Получается нужно делать какие-то хуки на уровне окружения. Проверять лог отправки. В случае парсинга RSS и внешнего API подменять хост в hosts и дописывать дополнительный обработчик который будет отправлять/сохранять тестовые данные для подмененных внешних сервисов. Запускать придется в песочнице. А в случае запроса к сервисам по IP придется еще заморачиваться с перенаправлением трафика.
Та еще развлекуха.

На тему test-first. Я согласен что во многих случаях написать тест до реализации не проблема. Тут я не спорю. Я просто приведу пример простого на вид кода и его теста, возможно на их примере будет понятно что написать тест до реализации может быть не так уж и просто.

Тест
$channel = 'foo';
$date_start = new \DateTime('2016-01-11');
$date_end = clone $date_start;
$date_end->modify('+7 day');
$result = [['2016-01-11', 10]];

$sth = $this->getMockBuilder(Statement::class)
    ->disableOriginalConstructor()
    ->getMock();
$sth
    ->expects($this->once())
    ->method('fetchAll')
    ->will($this->returnValue($result));

$i = 0;
$builder = $this->getMockBuilder(QueryBuilderDBAL::class)
    ->disableOriginalConstructor()
    ->getMock();
$builder
    ->expects($this->at($i++))
    ->method('select')
    ->with('e.date, COUNT(*) AS `total`')
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('from')
    ->with('schedule_event', 'e')
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('where')
    ->with('e.date >= :date_start')
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('andWhere')
    ->with('e.date < :date_end')
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('andWhere')
    ->with('e.channel = :channel')
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('andWhere')
    ->with('e.movie_id IS NOT NULL')
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('groupBy')
    ->with('date')
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('setParameter')
    ->with(':date_start', $date_start->format('Y-m-d'))
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('setParameter')
    ->with(':date_end', $date_end->format('Y-m-d'))
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i++))
    ->method('setParameter')
    ->with(':channel', $channel)
    ->will($this->returnSelf());
$builder
    ->expects($this->at($i))
    ->method('execute')
    ->will($this->returnValue($sth));

$exp = $this->getMockBuilder(ExpressionBuilder::class)
    ->disableOriginalConstructor()
    ->getMock();
$exp
    ->expects($this->once())
    ->method('isNotNull')
    ->with('e.movie_id')
    ->will($this->returnValue('e.movie_id IS NOT NULL'));

$conn = $this->getMockBuilder(Connection::class)
    ->disableOriginalConstructor()
    ->getMock();
$conn
    ->expects($this->once())
    ->method('createQueryBuilder')
    ->will($this->returnValue($builder));
$conn
    ->expects($this->once())
    ->method('getExpressionBuilder')
    ->will($this->returnValue($exp));

$em = $this->getMockBuilder(EntityManager::class)
    ->disableOriginalConstructor()
    ->getMock();
$em
    ->expects($this->once())
    ->method('getConnection')
    ->will($this->returnValue($conn));

$class = $this->getMockBuilder(ClassMetadata::class)
    ->disableOriginalConstructor()
    ->getMock();

$rep = new ScheduleEvent($em, $class); // EntityRepository

$this->assertEquals($result, $rep->getAllWeekEvents($day_start, $channel));


Тестируемый код
Это только треть всего метода. Там еще один такой запрос и обработка результата. Для примера я думаю и этого будет достаточно
function getAllWeekEvents(\DateTime $date_start, $channel)
{
    $date_end = clone $date_start;
    $date_end->modify('+7 day');

    return $this
        ->getEntityManager()
        ->getConnection()
        ->createQueryBuilder()
        ->select('e.date, COUNT(*) AS `total`')
        ->from('schedule_event', 'e')
        ->where('e.date >= :date_start')
        ->andWhere('e.date < :date_end')
        ->andWhere('e.channel = :channel')
        ->andWhere($this->getEntityManager()->getConnection()->getExpressionBuilder()->isNotNull('e.movie_id'))
        ->groupBy('date')
        ->setParameter(':date_start', $date_start->format('Y-m-d'))
        ->setParameter(':date_end', $date_end->format('Y-m-d'))
        ->setParameter(':channel', $channel)
        ->execute()
        ->fetchAll();
}

в вики неплохо объясняется разница между black-box и white-box тестированием
Вот кстати я не до конца понимаю этот момент. Если мы мочим реализацию public методов зависимостей, которые потом использует
тест, это считается black box или нет? С одной стороны мы предполагаем что должно происходить внутри метода, с другой стороны нас это не интересует, так как нам важно получить нужный assert и не важно как метод это будет делать.

это считается white-box testing
1. Не разумно выносить в функцию? Вам необходимо вводить отдельный класс, обязанностью которого будет осуществление рассылки и подключать его как зависимость.

А почему мне именно «необходимо» вводить отдельный класс. Если при рассылке делается что-то больше чем проход по циклу и передача каждого значения функции отправки, то да, скорей всего понадобится вводить еще один уровень абстракции.
Если ваша функция называется «Получить всех пользователей И отправить им сообщение», то нужно разделять, а если функция называется «Отправить все пользователям сообщение» то разносить нечего. Я согласен что здесь можно, а иногда и нужно, вводить еще один уровень абстракции, но почему вы считаете что это единственно верный способ? Так ли необходимо создавать еще один класс из-за этих 3 строчек:

foreach ($this->rep->getUserEmails() as $email) {
    $this->mailer->send($email, $message);
}


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

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

Если вы пишите тесты перед реализацией то реализация представляет собой чистейший черный ящик, потому что реализации ещё нет.

Тут вы не правы. Тестирование по принципу black-box означает тестирование реакции на воздействие абстрагированное от внутренней реализации. То есть мы тестируем только выходные значения для заданных входных.
В случае же test-first, мы действительно не знаем ничего о внутренней реализации, потому что ее еще нет, но мы можем управлять внутренней реализацией. Мокнуть сервис, проверить передаваемые ему параметры.

Говоря другими словами, в случае black-box если тестируемая функция записывает что то в БД, то мы должны проверить наличие этой записи в бд, а в случае test-first мы должны проверить что сервису БД были переданные данные на запись.
Тут, как минимум, две отдельные функции:
— рассылка письма по списку/массиву/коллекции/итератору/… адресов
— получение списка/массива/коллекции/итератора/… адресов

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

  • мы валидируем данные от пользователя;
  • создает сообщение на основе данные от пользователя;
  • получаем список получаетелей сообщения;
  • отправляем каждому получателю сообщение;
  • отдаем пользователю результат отправки.


Я привожу очень условный пример. Понятно что если адресов 100500, то это как минимум cli команда, а в идеале еще и очередь используется что-то типа RabbitMQ, но суть то не в этом. Суть в том что функции высокого уровня абстракции может быть очень сложно протестировать по принципу black-box, а порой и вовсе невозможно.

Как это не можем точно знать? Зачем мы их вводим, если не знаем как будем использовать?

Пример я привел. Мы точно знаем что нам необходимо получить данные из бд, то есть будет использоваться драйвер бд (например EntityManager из Doctrine), но мы не знаем какой из методов получения будет использоваться и как. В некоторых случаях мы можем точно знать как будет использоваться зависимость, но не во всех.
Мне всё время было интересно как это, тестирование по принципу черного ящика. Везде пишут простые примеры сложения и перемножения переменых, а как на счёт чего-то действительно сложного, например:

  • Рассылка письма из параметров функции по базе из 100500 email адресов;
  • Парсинг стороннего RSS и сохранение новостей в бд;
  • Push данных в стороннее API.


В случае например с email рассылкой. Понятно что функция использует какой-то драйвер БД и для отправки сообщения конкретному получателю используется какой-то сервис. Но если мы тестируем по принципу чёрного ящика, то мы ничего не знаем о внутренней реализации и завистмостях и мы должны вызывать эту функцию и проверять все 100500 ящиков.

Понятно что в обычном тестировании мы можем мокнуть сервис отправки сообщений и драйвер БД, но в этом случае мы уже начинаем привязываться к реализации. Дальше больше. Не стоит забывать о том что в нормальных драйверах БД есть несколько способов получит данные, например в Doctrine:

  • Метод из репозитория;
  • ORM QueryBuilder;
  • DBAL QueryBuilder;
  • DQL;
  • SQL.


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

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

Я себе представляю как я буду писать тест на ещё не созданый запрос построеный с помощью QueryBuilder.

PS: Если кому интересно, могу привести пример как запрос на 15 строк превращается в тест на 93 с жёсткой привязкой к порядку вызова методов.
4) Я не имел в виду конкретно хостинг. Тот же VPS может быть с ограниченным трафиком
7) Весь кеш все равно не сгенерируешь и сервер все равно придется ждать

В общем это довольно спорный момент. Нужно пробовать оптимизировать на конкретном проекте очень аккуратно и с умом. Если делать в лоб, то можно и потерять в скорости. Основная моя мысль была в этом.
Статья интересная. Автору спасибо. Хоть я и не увидел ничего нового, но кое с чем я с автором не согласен:
Мм, окей. Давайте тогда объединим все-все js файлы в 1 и все-все css файлы в 1. Тогда же уместимся в лимиты любого браузера, ну и грузиться будет быстро.

На правильном пути, но это всё ещё некорректно. Вам не нужны все-все js и css файлы для начальной отрисовки страницы. Даже если часть не используется — они все равно занимают канал и процессорное время. Ну и ещё при любом изменении самого маленького файлика кэш инвалидируется и пользователю загружать всё заново.

Я раньше много экспериментировал с оптимизацией, сжатием и http кешем и пришел к следующему выводу: в случае css лучше объединять все в 1 файл, а в случае js можно попробовать разбить на компоненты, но аккуратно

  1. если дополнительные стили подгружать через js мы потеряем время на исполнение js кода отвечающего за загрузку стилей
  2. если загружать через link то потерь будет меньше, но появится проблема с очередью загрузки, ведь мы хотим что бы дополнительные стили грузились после загрузки основных данных. В случае загрузки дополнительных js модулей их можно разместить в конце html документа
  3. мы потеряем время на загрузку дополнительных стилей. еще запрос, ожидание, загрузка, рендор. В сумме это будет дольше чем 1 большой запрос. Для css это критично, а для js нет
  4. мы увеличиваем количество запросов к серверу, что может быть критично для сервера, канала и тарифного плана хостера
  5. в данном случае мы рассматриваем первую загрузку на холодном кеше, при следующем запросе css и js будут браться из локального кеша браузера и в этом случае будет быстрее если это будет один файл. Даже если данные запрашиваются из кеша, это все равно запрос и чем меньше запросов тем быстрее
  6. аргумент с изменением файлов тоже не аргумент потому что это относится к последующим запросам, а файл css или js меняется, если меняется, только после деплоя который в нормальных проектах делается не более 2 раз в неделю. Если изменения произошли в 3 файлах из 5, то мы загрузим 3 новых файла или 1 если все 5 файлов сжаты в 1. Объем больше, но запросов меньше
  7. вообще в случае деплоя нормально сбрасывать весь кеш и в этом случае первый пользователь после деплоя генерит не только локальных кеш, но и кеш на сервере и от этого никуда не деться
  8. так же не стоит забывать об обфускации css и js кода которая может дать до 40% уменьшения объема и тем самым повышение скорости загрузки
  9. можно еще использовать gzip компрессию уменьшив размер файлов еще на ~5%. Но я правда не знаю хранит ли браузер в локальном кеше сжатую версию или нет. Если хранит сжатую то на каждый запрос браузер будет тянуть из кеша, разжимать и применять. В этом случае сжатие будет только вредить
  10. реальная экономия получится только если будет загружаться 0-2 из 10 дополнительных модулей/файлов, а если таких файлов 5-15 на запрос, то выигрыша особого уже не будет

Если js не влияет на визуализацию страницы, а только на функциональность то его можно разбить на модули и вынести в отдельные файлы. Пользователь переживет если раскрывашки, автокамплит, датапикер и тд будет работать не сразу как он открыл страницу, а вот кривую верстку он не переживет.
Толку не много потому что это только каталог шаблонов, не все из которых имеют смысл в php, но без понимания что такое «шаблоны проектирования» и например чем Singleton отличается от Lazy Load, далеко не уедешь.

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

Ну и про GRASP тоже книжка есть.
Ну, я бы не сказал что книги не стоит читать. Да, книги безусловно быстро устаревают, но читая статьи ты набираешь информации только поверхам, а для глубинного понимания и структурирования информации нужно читать книги.
Профессионалом не стать без прочения хотя бы GoF.
Я храню документацию на GitHub и вывожу ее на сайте. Написал hook который на pull request обновляет документацию на сайте. Таким образом получил live update документации на сайте и сайт не дергает GitHub без необходимости
а если сделать как советует korotovsky и вынести DataTransformer в отдельный класс то кода станет еще меньше
Проблема в том что в sonata используется устаревшая версия 3.1.3 от 16 августа 2014. В актуальной версии 4.17.37 от 10 сентября 2015 эту проблему исправили, но если просто перейти на версию 4.17.37 мы получим ошибку:
Uncaught TypeError: option pickTime is not recognized!

Генерируемый JavaScript код
$('#dtp_s5666a539ecb69_duration').datetimepicker({
    "pickTime":true,
    "useCurrent":true,
    "minDate":"1\/1\/1900",
    "maxDate":null,
    "showToday":true,
    "language":"ru",
    "defaultDate":"",
    "disabledDates":[],
    "enabledDates":[],
    "icons":{
        "time":"fa fa-clock-o",
        "date":"fa fa-calendar",
        "up":"fa fa-chevron-up",
        "down":"fa fa-chevron-down"
    },
    "useStrict":false,
    "sideBySide":false,
    "daysOfWeekDisabled":[],
    "useMinutes":true,
    "useSeconds":true,
    "minuteStepping":1
});


Соответственно необходимо переопределять поведение DateTimePickerType что бы поменять опции передаваемые datetimepicker-у или переопределять шаблон SonataCoreBundle:Form:datepicker.html.twig и писать в нем большой if-else.
В том то и дело что нет. В приведенном мною примере указывается формат HH:mm:ss, что говорит о том что дата выводится не должна и если создавать поля как в статье или как документации Bootstrap Datepicker, то оно так и работает. Однако, при использовании sonata_type_datetime_picker это не срабатывает. Очень похоже на то что sonata под капотом выполняет какие-то свои манипуляции с датой.
Сейчас попробовал использовать поле time из статьи совместно с sonata_type_datetime_picker и получил такой же результат как в моем комментарии.
И это не смотря на то что код JavaScript имеет вид
 $('.form-field-time').each(function () {
        var el = $(this),
            options = {locale: 'ru'};
        if (el.data('with-seconds') == 1) {
            options.format = 'HH:mm:ss';
        } else {
            options.format = 'HH:mm';
        }
        el.datetimepicker(options);
});


Завтра попробую подробней разобраться в проблеме.
Однако для выбора времени поле в существующем виде не подходит
выбор времени
$formMapper->add('duration', 'sonata_type_datetime_picker', [
    'label' => 'Продолжительность',
    'required' => false,
    'format' => 'HH:mm:ss',
    'date_format' => 'HH:mm:ss'
])

image

Information

Rating
Does not participate
Location
Россия
Registered
Activity