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

Установка и настройка PHPUnit. Написание Unit-тестов. Создание инфраструктуры функциональных тестов для API. Анализ тестового покрытия.
Чтобы не пропускать новые эпизоды подпишитесь на наш канал @deworkerpro в Telegram

спасибо за замечательный урок.
возможно ли тест: множественный запрос (напр. 100 раз в сек) от одного и того же IP, т.е. что-бы обнаружить атаку? как организована такая защита на Slim? как лучше организовать защиту? Будет о защите на уроках?
Да, добавим Rate Limiter.
Срасибо
Странно, чтоб сайт на PHP сам боролся с DDOS. Просто поставьте ограничение в фаерволе ведь он наверное для этого. Да и вообще по ситуации, есть различные ситуации - разные решения.
Согласен. Если по серьезному начнут ddos-ить, то в первую очередь ляжет сервер. До web приложения не дойдет
rate limit можно наложить на любой порт и на 80, 443 тоже.
В чем разница между PhpUnit и Codeception? Почему во всех уроках используете PhpUnit?
Codeception построен поверх PHPUnit, дополняя его своими модулями для готовых фреймворков и позволяя делать функциональные и приёмочные тесты в своём Cest-стиле.
Он с модулями удобен как быстрое решение для готовых фреймворков, но неудобен для кастомных проектов с вынесенным фронтендом.
Поэтому мы возьмём оригинальный PHPUnit для тестов API, отдельный JS-фреймворк для тестов JS и отдельный BDD-фреймворк для приёмочных тестов всей системы.
Дмитрий, спасибо за урок, все замечательно, а был ли у Вас опыт использования мутационного тестирования? Если да, то что думаете по этому поводу? Спасибо.
Особо не использовал из-за медленной работы, но вещь полезная.
Дмитрий, подскажите любезно, что с правами не так. Винда =/ Докер и консоль от администратора запущено. Докер ап работет исправно, сайт поднимается. А вот make init. Ерунда какая то, есть идеи? Спасибо.
docker-compose run --rm api-php-cli composer install
Loading composer repositories with package information
Nothing to install or update
Generating autoload files
docker run --rm -v /cygdrive/c/projects/demo-auction/api:/app -w /app alpine chmod 777 var
chmod: var: No such file or directory
make: *** [Makefile:27: api-permissions] Error 1
А что выводит команда ls ?
docker run --rm -v ${PWD}/api:/app -w /app alpine ls -l
$ docker run --rm -v ${PWD}/api:/app -w /app alpine ls -l
total 0
Docker-демон не видит виртуальную папку /cygdrive, которая. существует только в консоли Git.
Установите Make for Windows и запускайте команды в консоли PowerShell.
Установил эту утилиту, выполняю её из powerShell:
ocramius/package-versions: Generating version class...
ocramius/package-versions: ...done generating version class
docker run --rm -v /api:/app -w /app alpine chmod 777 var
chmod: var: No such file or directory
make: *** [api-permissions] Fehler 1
PS C:\Users\Symfony\Projects\demo-auction>
Странно, а остальные команды работают
В -v /api:/app адрес должен подставляться из ${PWD} абсолютный C:\Users\...
Странное поведение, потому что PWD в консоли работает:
PS C:\Users\Symfony\Projects\demo-auction> echo ${PWD}
Path
----
C:\Users\Symfony\Projects\demo-auction
Прописал прямо путь, ошибок не выбросило.
Nothing to install or update
Generating autoload files
ocramius/package-versions: Generating version class...
ocramius/package-versions: ...done generating version class
docker run --rm -v C:/Users/Symfony/Projects/demo-auction/api:/app -w /app alpine chmod 777 var
PS C:\Users\Symfony\Projects\demo-auction>
Значит та консоль подменяет $PWD на свой /cygdrive
Скорее всего. Но конкретно предыдущий лог из консоли это из powershell и make утилиты. и PWD в повершелле работает корректно.
PS C:\Users\Symfony\Projects\demo-auction> echo ${PWD}
Path
----
C:\Users\Symfony\Projects\demo-auction
Но ошибка все равно сыпется. Ерунда какая то :(
Я на винде прописал путь так и все работает: docker run --rm -v //${PWD}/api://app -w /app alpine chmod 777 var
Неа, с двойными слэшами тоже не работает :( Только абсолютный путь
У меня таже проблема, ломается на этом месте. Подскажите, что мы хотим добится в этом месте?
api-permissions:
docker run --rm -v ${PWD}/api:/app -w /app alpine chmod 777 var
Если только установить права на var. Но если ,я не запущу эту команду, то получу теже права:
PS C:\projects\Docker\eliseev\demo-auction> docker run --rm -v ${PWD}/api:/app -w /app alpine ls -l
total 152
drwxrwxrwx 1 root root 4096 Jan 23 21:20 bin
-rwxrwxrwx 1 root root 1037 Feb 4 09:57 composer.json
-rwxrwxrwx 1 root root 144228 Feb 4 09:57 composer.lock
drwxrwxrwx 1 root root 4096 Jan 23 21:20 config
drwxrwxrwx 1 root root 4096 Jan 21 15:16 docker
-rwxrwxrwx 1 root root 553 Feb 4 09:57 phpcs.xml
-rwxrwxrwx 1 root root 1011 Feb 4 09:57 phpunit.xml
-rwxrwxrwx 1 root root 670 Feb 4 11:05 psalm.xml
drwxrwxrwx 1 root root 4096 Jan 21 15:16 public
drwxrwxrwx 1 root root 4096 Jan 21 15:16 src
drwxrwxrwx 1 root root 4096 Feb 4 11:05 tests
drwxrwxrwx 1 root root 4096 Jan 23 17:52 var
drwxrwxrwx 1 root root 4096 Feb 4 12:11 vendor
Можно ли в данном случае обойтись без абсолютного пути? ${PWD}/api
Да, только устанавливаем права. Для docker-compose нужны относительные пути, а для docker требуются только абсолютные.
docker run --rm -v $$PWD/api:/app -w /app alpine ls -l
попробуйте так.
echo $$PWD
Дошел до 27 минуты, у меня не видны тесты:
PS C:\projects\Docker\eliseev\react_slim> docker-compose run --rm api-php-cli vendor/bin/phpunit
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.
Runtime: PHP 7.4.1 with Xdebug 2.9.1
Configuration: /app/phpunit.xml
Time: 1.3 seconds, Memory: 4.00 MB
No tests executed!
А phpunit.xml настроен верно?
Я в тупиковых ситуациях смотрю ваши комиты и беру от туда:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
executionOrder="depends,defects"
forceCoversAnnotation="true"
beStrictAboutCoversAnnotation="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
cacheResultFile="var/.phpunit.result.cache"
verbose="true">
<testsuites>
<testsuite name="default">
<directory suffix="Test.php">tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
<php>
<env name="APP_ENV" value="test" force="true"/>
<env name="APP_DEBUG" value="1" force="true"/>
</php>
</phpunit>
попробуйте выполнить команду composer dump-autoload
Всем добрый вечер. У кого то зависает загрузка видео на скорости 1.25 или 1.5? C интернетом перебоя нет. Это не в первый раз.
их не может быть, а если и случаются то зависят от Vimeo, так как он используется для размещения видео
Зависания были раньше, когда использовался файловый хостинг. Сейчас после перехода на Vimeo проблем со скоростью нет.
Дмитрий, я хотел сам поставить phpunit,
docker-compose run --rm api-php-cli composer require --dev phpunit/phpunit
не дало
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Conclusion: don't install phpunit/phpunit 9.2.4
- Conclusion: don't install phpunit/phpunit 9.2.3
- Conclusion: don't install phpunit/phpunit 9.2.2
- Conclusion: don't install phpunit/phpunit 9.2.1
- Conclusion: remove sebastian/diff 3.0.2
- Installation request for phpunit/phpunit ^9.2 -> satisfiable by phpunit/phpunit[9.2.0, 9.2.1, 9.2.2, 9.2.3, 9.2.4].
- Conclusion: don't install sebastian/diff 3.0.2
- phpunit/phpunit 9.2.0 requires sebastian/diff ^4.0 -> satisfiable by sebastian/diff[4.0.0, 4.0.1].
- Can only install one of: sebastian/diff[4.0.0, 3.0.2].
- Can only install one of: sebastian/diff[4.0.1, 3.0.2].
- Installation request for sebastian/diff (locked at 3.0.2) -> satisfiable by sebastian/diff[3.0.2].
Installation failed, reverting ./composer.json to its original content.
Ладно , взял код из комита - Added JSON response test
make init
docker-compose run --rm api-php-cli vendor/bin/phpinput --help
и в итоге - /usr/local/bin/docker-php-entrypoint: exec: line 9: vendor/bin/phpinput: not found
что где пропустил не пойму подскажите ?
Попробуйте удалить папку vendor и поставить через composer require снова. Если не получится, то сделайте сначала composer remove phpunit/phpunit, а потом снова requre.
phpunit в вендоре есть при любом раскладе и переустановке. Но все равно при запуске от любого коммита и после переустановки при вводе команды
docker-compose run --rm api-php-cli vendor/bin/phpinput --help
ответ один
/usr/local/bin/docker-php-entrypoint: exec: line 9: vendor/bin/phpinput: not found
Не phpinput, а phpunit
ага точно, походу ночью лучше спать ))))
Спасибо!
Добрый день, Дмитрий.
У меня phpunit 9.3.8 и psalm 3.14.2. Plalm выдает ошибку одинаковую для всех файлов Unit тестов, сами тесты работают нормально:
ERROR: PropertyNotSetInConstructor - src/Http/Test/Unit/EmptyResponseTest.php:10:7 - Property App\Http\Test\Unit\EmptyResponseTest::$backupStaticAttributes is not defined in constructor of App\Http\Test\Unit\EmptyResponseTest and in any methods called in the constructor (see https://psalm.dev/074)
class EmptyResponseTest extends TestCase
Подскажите в чем может быть проблема?
Можно добавить глобальное подавление этой ошибки для всех тестов в psalm.yml:
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<!-- PHPUnit -->
<PropertyNotSetInConstructor>
<errorLevel type="suppress">
<directory name="tests" />
</errorLevel>
</PropertyNotSetInConstructor>
</issueHandlers>
У меня phpunit 9 ругается на отсутствие @covers. Дописал к классу теста @covers JsonResponse и он перестал это делать.
Test\Unit\Http\JsonResponseTest::testObject This test does not have a @covers annotation but is expected to have one
UPD: досмотрел видео до конца и директивы в конфиге
У меня при запуску coverage тестов ругается
Configuration: /app/phpunit.xml Warning: XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set
Чтобы пофиксить в Ubuntu переделал команду в следующую:
"test-unit-coverage": "XDEBUG_MODE=coverage phpunit --colors=always --testsuite=unit --coverage-html var/coverage",
Было тоже самое, поменял в api/docker/development/php/conf.d/xdebug.ini xdebug.mode=debug на xdebug.mode=coverage. Лучше ничего не придумал пока)
Лучше как раз передавать XDEBUG_MODE=coverage при выполнении, как в совете выше.
Мы сделали также в 44-ом эпизоде про новый Xdebug 3
Отлично, спасибо!
Здравствуйте! Немного не в тему, но подскажите пожалуйста, есть ли в планах скринкасны по Python? Также охватывающие тему деполоя, сборки, тестов, контейнеризация.
Python в планах нет. А контейнеризация и деплой везде одинаковые.
Здравствуйте. Подскажите такой момент: У меня на testMethod и на testNotFound валятся ошибки Error: Value of type bool is not callable при этом если их по отдельности по 1 запустить то они нормально проходят, я понимаю что это из за того что экземпляр приложения после первого теста создан, и при попытки создать его еще раз и валится эта ошибка. Только вот я не понимаю как ее обойти. версия slim 4.9 версия phpunit 9.5
Вопрос отпал, у мена в файле config/app.php были прописаны require_once а не просто require
Здравствуйте! При запуске
docker-compose run --rm api-php-cli composer test-coverage -- --testsuite=unit
ошибка
Fatal error: Uncaught Error: Class 'PHP_Token_CLASS' not found in /app/vendor/phpunit/php-token-stream/src/Stream.php:477
команда в Composer
"test-coverage": "phpunit --colors=always --coverage-html var/coverage"
С чем это может быть связано?
Может проблема текущей верси PHPUnit.
здравствуйте, меня всегда мучает такой вопрос: как тестить сервисы с более менее сложным функционалом? Или все надо вынести в отдельные функции так, чтобы не было сложной логики и тогда не будет необходимости тестить основной сервис который оперирует маленькими функциями?
пример такого сервиса:
public function getSynched(int $customerId): Customer
{
$customer = $this->getUnSynched($customerId);
try {
$results = $this->mwService->request('post', '/billing/api/customer/info/', [
'customer_id' => $customerId
]);
} catch (MwException $e) {
if ($e->getCode() === 7001) {
CustomerRepository::delete($customer);
}
throw $e;
}
$customer->setAbonement($results['accounts'][0]['abonement']);
$customer->updateBalanceFromSom($results['balance']);
$customer->sync();
$customer->save();
foreach ($results['subscriptions'] as $remoteSubscription) {
if ($remoteSubscription['end_date'] === null) {
continue;
}
if (!$tariff = TariffRepository::findById($remoteSubscription['tariff_id'])) {
$tariff = Tariff::make($remoteSubscription['tariff_id'], $remoteSubscription['price']);
$tariff->save();
}
/** @var Subscription $localSubscription */
$localSubscription = SubscriptionRepository::findByCustomerAndTariffId($customer, $remoteSubscription['tariff_id']);
if ($remoteSubscription['is_periodical'] == 0) {
if (!$localSubscription) {
$expiredAt = Carbon::parse($remoteSubscription['end_date']);
$localSubscription = $this->subscriptionService->create($customer, $tariff, $expiredAt);
}
$localSubscription->cancel();
$localSubscription->save();
continue;
}
if ($localSubscription) {
$localSubscription->updatePriceFromSom($remoteSubscription['price']);
if (isset($remoteSubscription['end_date']) && $localSubscription->isSyncExpired()) {
$expiredAt = Carbon::parse($remoteSubscription['end_date']);
$localSubscription->renew($expiredAt);
}
} else {
if (isset($remoteSubscription['end_date'])) {
$expiredAt = Carbon::parse($remoteSubscription['end_date']);
$localSubscription = $this->subscriptionService->create($customer, $tariff, $expiredAt);
} else {
$localSubscription = $this->subscriptionService->create($customer, $tariff);
}
}
$localSubscription->save();
}
$customer->subscriptions()
->whereNotIn('tariff_id', array_column($results['subscriptions'], 'tariff_id'))
->delete();
return $customer;
}
Да, всё так. Либо тестировать большой сервис только интеграционными тестами, либо разбить на отдельные подменяемые моками и стабами части, которые можно будет протестировать отдельными юнит-тестами.
Стало видео зацикливается. Причем даже другую точку на видео не возможно выбрать. Дмитрий, посмотрите?
Напишу что все исправлено!
Большая просьба. Добавьте пожалуйста в .gitignore папки .idea.
У всех редакторы могут быть разные. В Git для этого можно указать глобальный игнор:
и добавлять папки своих редакторов туда:
Спасибо
Большое спасибо!