Как сделать тест на php

Добавил пользователь Владимир З.
Обновлено: 05.10.2024

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Написание тестов для PHPUnit

Пример 2.1 показывает как выгляит тест для проверки встроенных функий для работы с массивами. В этом примере представлены основные соглашения и шаги при написание тестов с помощью PHPUnit:

  1. Тесты для класса Class именуются как ClassTest .
  2. ClassTest наследуется (в большинстве случаев) от PHPUnit\Framework\TestCase .
  3. В качестве тестов выступают публичные методы класса начинающиеся с test* . Другой способ указать на тестовый метод - использовать @test аннотацию в docblock комментарие.
  4. Внутри тестового метода методы утверждений, такие как assertEquals() , используются чтобы проверить соответствие факстического и ожидаемого значения.

Пример 2.1: Тестирование операций для работы с массивами с помощью PHPUnit

Всякий раз, когда возникает соблазн вывести что-то с помощью print или отладчика, напишите его как тест. - Martin Fowler

Модульные тесты в первую очередь написаны как хорошая практика, помогающая разработчикам выявлять и исправлять ошибки, проводить рефакторинг кода и служить документацией для тестируемого модуля. Для достижения этих преимуществ unit-тесты в идеале должны охватывать все возможные пути в программе. Один тест обычно охватывает один конкретный путь в одной функции или методе. Однако, метод проверки не нужен инкапсулированному независимому объекту. Часто существуют неявные зависимости между методами тестирования скрытые в сценарии реализации теста. - Adrian Kuhn et. al.

PHPUnit поддерживает декларацию явных зависимостей между методами тестирования. Такие зависимости не определяют порядок в котором должны выполняться тестовые методы, но они позволяют возвращать экземпляр тестовой фикстуры от производителя(producer) и передавать его зависимым потребителям(consumers).

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

В примере 2.2 показано как использовать декларацию @depends для определения зависимостей между методами.

Пример 2.2: Использование @depends для определения зависимостей между методами.

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

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

Пример 2.3: Использование зависимостей между тестами.

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

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

Пример 2.4: Тест с несколькими зависимостями

Тестовый метод может принимать произвольные аргументы. Эти аргументы могут быть получены с помощью метода провайдера данных( additionProvider() см. Пример 2.5). Используемый поставщик данных, указывается с помощью аннотации @dataProvider .

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

Пример 2.5: Использование провайдера данных возвращающего массив массивов

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

Пример 2.6: Использование провайдера с именованными данными

Пример 2.7: Использование провайдера данных возврщающего Iterator

Когда тест получает входные данные как от @dataProvider , так и от одного или нескольких тестов, от которых он зависит @depends , то аргументы от поставщика данных будут идти до аргументов полученных от зависимых тестов. Апи этом аргументы от зависимых тестов будут одинаковыми для каждого набора данных провайдера. См. Пример 2.9

Пример 2.9: Одновеменное использование @depends и @dataProvider в одном тесте

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

Note: Все поставщики данных выполняются перед вызовом статического метода setUpBeforClass и первым вызовом метода setUp . Из-за этого вы не можете получить доступ к любым переменным, которые вы создаете внутри поставщика данных. Это необходимо для того, чтобы phpunit мог вычислить общее количество тестов.

В примере 2.10 показано как использовать метод expectException() для проверки генерации исключения в тестируемом коде.

Пример 2.10: Использование метода expectException()

Помимло метода expectException() так же существуют другие методы для проверки генерации исключений expectExceptionCode() , expectExceptionMessage() и expectExceptionMessageRegExp() .

Также вы можете использовать аннотации @expectedException , @expectedExceptionCode , @expectedExceptionMessage и @expectedExceptionMessageRegExp как показано в примере 2.11ю

Пример 2.11: Использование аннтотации @expectedException

Тестирование ошибок PHP

По умолчанию PHPUnit конвертирует PHP errors , warnings и notices , возникающие во время выполнения, в исключения. Используя эти исключения вы можете, например, ожидать что тест вызовет ошибку php как показано в примере 2.12.

Note: Так же можно использовать error_reporting чтобы указать какие ошибки PHPUnit будет конвертировать. Если у вас возникли проблемы с этой функцией убедитесь что php не настроен на подавление типа ошибок которые вы тестируете.

Пример 2.12: Обработка ошибок PHP используя аннтотацию @expectedException

PHPUnit_Framework_Error_Notice and PHPUnit_Framework_Error_Warning соответственно представляют PHP notices и warnings.

Note: Будьте более конкретными при тестировании исключений. Тестирование слишком общих классов может привести к нежелательным побочным эффектам. Поэтому использование @expectedException или setExpectedException() для тестирования класса Exception не разрешено.

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

Пример 2.13: Проверка возвращаемого значения в коде использующем php-ошибки

Без подавления ошибок тест завершиться с ошибкой fopen(/is-not-writeable/file): failed to open stream: No such file or directory

Иногда нужно проверить результат выполенения метода сгенертрованный в стандартный вывод (например, через echo или print ). Данная возможность осущетсвляется классом PHPUnit\Framework\TestCase с помощью Функции контроля вывода. В примере 2.14 показано как проверить ожидаевый результат вывода с помощью функции expectOutputString() . Вслучае если ожидаемый вывод не сгенерирован, тест считается не проеденным.

Пример 2.14: Проверка результата вывода для функции или метода

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

Таблица 2.1: Методы для тестирования вывода

Метод Описание
void expectOutputRegex(string $regularExpression) Сопоставляет ожидаемый вывод с регулярным выражением $regularExpression .
void expectOutputString(string $expectedString) Сопоставляет ожидаемый вывод со строкой $expectedString .
bool setOutputCallback(callable $callback) Устанавливает функцию для нормализации текущего вывода.
string getActualOutput() Возвращает текущее значение вывода.

Тесты которые генерируют вывод не будут пройдены в strict mode .

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

Пример 2.15: Генерация ошибке для не пройденного теста сравнения массивов

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

Пример 2.16: Генерация ошибке для не пройденного теста сравнения больших массивов

В этом примере показана разница в первом индексе между 1 и '1' хотя функция assertEquals считает их одинаковыми.

создание системы тестирования

От автора: В цикле из нескольких уроков мы с вами будем создавать несложную, но вместе с тем функциональную систему тестирования, которая должна в первую очередь выполнять свою прямую задачу – оценка прохождения теста тестируемым.


скачать урок
скачать исходники

Все уроки курса:

Комментарии (20)

Здравствуйте. Прошу вас. Умоляю. Отправьте мне админ панель с регистрацией и возможностью комментировать тесты

Здравствуйте, Андрей. Уроков по созданию админки нет, соответственно, нет и самой админки, поэтому отправлять просто нечего.

Доброго дня!
Вопрос по уроку при выводе массива (20 мин.)
Ругается на 17 строку кода
«Warning: mysqli_query() expects parameter 1 to be mysqli, resource given in J:\home\localhost\www\testing\functions.php on line 17″
сама строка в коде прописана вот так: $res = mysqli_query($db, $query);
где ошибка не могу понять.
Подскажите пожалуйста.

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

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

Для сервера несколько тысяч записей — это вообще не нагрузка. Сервер без проблем может оперировать и сотнями тысяч записей, если архитектура БД спроектирована грамотно.

Буду знать. Спасибо!

Скажите а как сделать что бы он сохранял данные пользователя с общим счетом?

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

Андрей, добрый день! Не нашел обсуждения этой темы на форуме, поэтому пишу здесь. Не могли бы Вы подробно объяснить (лучше в личной переписке) работу строки Войдите, чтобы ответить

Здравствуйте, Андрей. Если темы на форуме нет, тогда Вы можете создать ее сами и задать вопрос, логично? По Вашему вопросу здесь. Через вопросительный знак на сервер можно передать GET параметры. В данном случае передается параметр test и на сервере он будет доступен в массиве $_GET['test']. Как-то так. Если интересует какой-то другой аспект, тогда на форуме задайте вопрос как можно конкретнее, и я постараюсь ответить.

Записываются данные в БД с помощью запроса INSERT, как Вы и делаете. Если данные не сохраняются, значит есть проблема с логикой или в запросе. Здесь поможет только отладка кода. Начните с проверки, попадаете ли Вы в условие и доходит ли дело до запроса. Для этого достаточно распечатать запрос на экран. Если Вы увидите распечатку запроса, значит с логикой кода все в порядке, в этом случае проверьте корректность запроса — в нем должны быть верно перечислены поля и должны быть все соответствующие значения. Сам распечатанный запрос можно даже скопировать и выполнить в phpMyAdmin, чтобы убедиться в том, что он (не)работает. Как-то так.

В общем проблема как я понял была в запросе, я немного исправил его и добавил or die(mysqli_error($db)), теперь выводит
INSERT INTO testing.result (result_id, parent_user, parent_test, mark) VALUES (NULL, ‘Ivanov Ivan Ivanovich’, ’1′, ’66.67′)
Cannot add or update a child row: a foreign key constraint fails ( testing . result , CONSTRAINT result_ibfk_2 FOREIGN KEY ( parent_test ) REFERENCES test ( test_name ))
Простите что здесь спрашиваю

У Вас используется внешний ключ для связи таблиц, который накладывает какие-то ограничения. Либо уберите внешний ключ, либо согласуйте данные, чтобы они соответствовали имеющимся ограничениям. Это все, что могу пока подсказать. Судя по всему, проблема с полем parent_test, в которое Вы пытаетесь записать цифру 1, но которое ссылается на поле test.test_name, где, очевидно, но ID теста, а его номер. Хотя могу и ошибаться.

Андрей, помогите. Не работает html код, php выполняется, а html нет, если меняю название файла на index.html, то html работает, а php нет. В чем причина, помогите

Для того, чтобы исполнялся код PHP — расширение файла должно быть .php, не .html. Ну и, конечно же, на сервере должен быть установлен интерпретатор PHP. Если эти условия выполняются, тогда код PHP должен исполняться. Поэтому проверяйте эти моменты.

Количество уроков: 12

Продолжительность курса: 06:54:25

Автор: Андрей Кудлай

Описание курса: В курсе по созданию системы тестирования на PHP мы создадим несложную, но вместе с тем функциональную систему тестирования, которая будет в первую очередь выполнять свою прямую задачу – оценка прохождения теста тестируемым.

Все права защищены © 2022
ИП Рог Виктор Михайлович
ОГРН: 313774621200541
Служба поддержки

А/Б-тестирование (или сплит-тестирование) – это проверка, какой из двух или более вариантов интерфейса (или какого-либо взаимодействия с пользователем) приносит больше прибыли. Тестировать можно фразы, внешний вид и расположение элементов и даже разные версии сайта целиком.

Подключение¶

    Скачайте файл Программируемый тест и поместите его в любую папку на сервере.

Создайте файл tests.php, в котором будет находиться список всех ваших А/Б-тестов, и поместите его в ту же самую папку, где расположен ABTest.php.

Пример файла tests.php:

В начале файла, отдающего страницу, подключите библиотеку для А/Б-тестирования:

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

Одностраничный сайт¶

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

Многостраничный сайт¶

Если требуется проводить разные тесты на разных страницах, все тесты по умолчанию нужно пометить как неактивные. В tests.php установите поле 'active' => false :

На каждой странице активируйте нужные тесты вручную:

Сравнение двух разных версий сайта¶

Если вы хотите сравнить две версии сайта (например, две посадочные страницы):

  1. Сохраните оригинальный реферер в cookie.
  2. Настройте переадресацию на вторую страницу, сохраняя все метки в ссылке.

Пример для двух страниц на одном домене¶

В tests.php нужно задать вариант теста:

tests.php

На странице, которая является исходным вариантом, нужно вставить код:

Пример для двух страниц на разных доменах¶

В tests.php нужно задать вариант теста:

tests.php

На странице, которая является исходным вариантом, нужно вставить код:

Вставьте код в начало страницы, на которую ведет переадресация (до загрузки контента страницы):

Получение результатов¶

В пункте меню А/Б-тесты в Roistat начнет накапливаться статистика, которая поможет вам выбрать самый эффективный вариант:


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

Содержание

  1. Тестовые дубли
  2. Наименования
  3. Шаблон AAA
  4. Мать объекта
  5. Параметризированный тест
  6. Две школы юнит-тестирования
    • Классическая
    • Моковая
    • Зависимости
  7. Моки и заглушки
  8. Три стиля юнит-тестирования
    • Результат
    • Состояние
    • Взаимодействие
  9. Функциональная архитектура и тесты
  10. Наблюдаемое поведение и подробности реализации
  11. Единица поведения
  12. Шаблон humble
  13. Бесполезный тест
  14. Хрупкий тест
  15. Исправления тестов
  16. Общие антипаттерны тестирования
    • Раскрытие приватного состояния
    • Утечка подробностей о предметой области
    • Мокинг конкретных классов
    • Тестирование приватных методов
    • Время как непостоянная зависимость
  17. Не гонитесь за полным покрытием
  18. Рекомендуемые книги

Тестовые дубли

Это фальшивые зависимости, используемые в тестах.

Заглушки (Stub)

Имитатор (Dummy)

Имитатор — всего лишь простая реализация, которая ничего не делает.

Фальшивка (Fake)

Фальшивка — это упрощённая реализация, эмулирующая нужное поведение.

Заглушка (Stub)

Заглушка — это простейшая реализация с прописанным в коде поведением.

Моки (Mock)

Шпион (Spy)

Шпион — реализация для проверки конкретного поведения.

Мок (Mock)

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


! Для проверки входящий взаимодействий используйте заглушку, а для проверки исходящих взаимодействий — мок. Подробнее об этом в главе Моки и заглушки.

Наименования


Явно указывайте, что вы тестируете.

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

Описание поведения важно при тестировании предметных сценариев. Если ваш код утилитарный, то это уже не так важно.

Шаблон AAA

Выделяйте в тестах три этапа:

  • Arrange: приведите тестируемую систему к нужному состоянию. Подготовьте зависимости, аргументы, и создайте SUT.
  • Act: извлеките тестируемый элемент.
  • Assert: проверьте результат, финальное состояние или взаимодействие с другими объектами.

Мать объекта

Параметризированный тест

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

Две школы юнит-тестирования

Классическая (Детройтская школа)

  • Модуль — это единица поведения, может состоять из нескольких взаимосвязанных классов.
  • Все тесты должны быть изолированы друг от друга. Должна быть возможность вызывать их параллельно или в произвольном порядке.

Моковая (Лондонская школа)

  • Модуль — это один класс.
  • Модуль должен быть изолирован от взаимодействующих объектов.

Классический подход лучше позволяет избегать хрупких тестов.

Зависимости

Моки и заглушки

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

Три стиля юнит-тестирования

Результат

  • Наилучшая сопротивляемость рефакторингу.
  • Наилучшая точность.
  • Меньше всего усилий по сопровождению.
  • Если возможно, применяйте этот вид тестов.

Состояние

Взаимодействие

  • Худшая сопротивляемость рефакторингу.
  • Худшая точность.
  • Сложнее всего в сопровождении.

Функциональная архитектура и тесты


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

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

Наблюдаемое поведение и подробности реализации


У первой модели подписки плохая архитектура. Для вызова одной бизнес-операции нужно вызывать три метода. Также не рекомендуется использовать методы-получатели (геттеры) для проверки операции. В данном примере пропущена проверка изменения modifiedAt . Возможно, указание конкретного modifiedAt в ходе операции renew можно протестировать с помощью бизнес-операции устаревания. Для modifiedAt метод-получатель не требуется. Конечно, есть ситуации, в которых очень трудно найти способ избежать использования методов-получателей только для тестов, но их нужно избегать всеми силами.

Единица поведения


Не пишите код 1:1: один класс — один тест. Это приводит к хрупким тестам, что затрудняет рефакторинг.

Шаблон humble

Как правильно выполнять юнит-тестирование такого класса?


Нужно разбить чрезмерно усложнённый код на отдельные классы.


Однако ApplicationService , вероятно, нужно проверить с помощью интеграционного теста с моком FormApiInterface .

Бесполезный тест


Тестировать код, не содержащий какой-либо сложной логики, не только бессмысленно, но и приводит к хрупким тестам.

Хрупкий тест


Подобное тестирование репозиториев приводит к хрупким тестам и затрудняет рефакторинг. Тестируйте репозитории с помощью интеграционных тестов.

Исправления тестов

  • Лучше избегать использования общего для нескольких тестов состояния.
  • Чтобы повторно использовать элементы в нескольких тестах применяйте:

Общие антипаттерны тестирования

Раскрытие приватного состояния


Внесение дополнительного production-кода (например, метода-получателя getCustomerType() ) только ради проверки состояния в тестах — плохая практика. Состояние нужно проверять другим важным предметным значением (в этом случае — getPercentageDiscount() ). Конечно, иногда трудно найти другой способ проверки операции, и мы можем оказаться вынуждены внести дополнительный production-код для проверки корректности тестов, но нужно стараться избегать этого.

Утечка подробностей о предметной области


Не дублируйте в тестах production-логику. Проверяйте результаты с помощью прописанных в коде значений.

Мокинг конкретных классов


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

Тестирование приватных методов


Тесты должны проверять только публичный API.

Время как непостоянная зависимость

Время является непостоянной зависимостью из-за своего недетерминизма. Каждый вызов даёт другой результат.


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

Не гонитесь за полным покрытием

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

Читайте также: