私は、企業チャットでHeisenbug 2021からのレポートについて話し合うことにより、この記事を書くように促されました。これは、テストの「正しい」記述に多くの注意が払われているという事実によるものです。引用符で-紙の上ではすべてが本当に論理的で合理的であるためですが、実際にはそのようなテストはかなり遅いことがわかります。
この記事はプログラミングの初心者を対象としていますが、おそらく誰かが以下に説明するアプローチの1つに触発される可能性があります。
私は誰もが良いテストの原則を知っていると思います:
, .. (, HTTP- )
, ..
, .. CI
, : pipeline 3000 !
, , , .. . .
API :
( )
( )
HTTP-
, , . HTTP API, API.
( , ) , . , , : Redis/RabbitMQ HTTP , .
, DI- .
:
{
"method": "patch",
"uri": "/v2/project/17558/admin/items/physical_good/sku/not_existing_sku",
"headers": {
"Authorization": "Basic MTc1NTg6MTIzNDVxd2VydA=="
},
"data": {
"name": {
"en-US": "Updated name",
"ru-RU": " "
}
}
}
{
"status": 404,
"data": {
"errorCode": 4001,
"errorMessage": "[0401-4001]: Can not find item with urlSku = not_existing_sku and project_id = 17558",
"statusCode": 404,
"transactionId": "x-x-x-x-transactionId-mock-x-x-x"
}
}
<?php declare(strict_types=1);
namespace Tests\Functional\Controller\Version2\PhysicalGood\AdminPhysicalGoodPatchController;
use Tests\Functional\Controller\ControllerTestCase;
class AdminPhysicalGoodPatchControllerTest extends ControllerTestCase
{
public function dataTestMethod(): array
{
return [
// Negative cases
'Patch -- item doesn\'t exist' => [
'001_patch_not_exist'
],
];
}
}
:
TestFolder
├── Fixtures
│ └── store
│ │ └── item.yml
├── Request
│ └── 001_patch_not_exist.json
├── Response
│ └── 001_patch_not_exist.json
│ Tables
│ └── 001_patch_not_exist
│ └── store
│ └── item.yml
└── AdminPhysicalGoodPatchControllerTest.php
, . json yml ( ), ( ).
...
, , , , .
1.
— , .
( , ), . , , .
— .
— , ? .. ?
, 1 , , , , ! .
2.
, . , ( ).
667 . . , ?
, , CI-.
#!/usr/bin/env bash
if [[ ! -f "dump-cache.sql" ]]; then
echo 'Generating dump'
#
migrations_dir="./migrations" sh ./scripts/helpers/fetch_migrations.sh
#
migrations_dir="./migrations" host="percona" sh ./scripts/helpers/migrate.sh
# (store, delivery)
mysqldump --host=percona --user=root --password=root \
--databases store delivery \
--single-transaction \
--no-data --routines > dump.sql
cp dump.sql dump-cache.sql
else
echo 'Extracting dump from cache'
cp dump-cache.sql dump.sql
fi
CI-job (gitlab)
build migrations:
stage: build
image: php72:1.4
services:
- name: percona:5.7
cache:
key:
files:
- scripts/helpers/fetch_migrations.sh
paths:
- dump-cache.sql
script:
- bash ./scripts/ci/prepare_ci_db.sh
artifacts:
name: "$CI_PROJECT_NAME-$CI_COMMIT_REF_NAME"
paths:
- dump.sql
when: on_success
expire_in: 30min
3.
. , , . :
:
19 ( 27 ) 10 ( ): 10 18 .
:
, . , DI-.
AUTO INCREAMENT , TRUNCATE. , .
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
foreach (self::$onSetUpCommandArray as $command) {
self::getClient()->$command(self::getFixtures());
}
}
...
/**
* @dataProvider dataTestMethod
*/
public function testMethod(string $caseName): void
{
/** @var Connection $connection */
$connection = self::$app->getContainer()->get('doctrine.dbal.prodConnection');
$connection->beginTransaction();
$this->traitTestMethod($caseName);
$this->assertTables(\glob($this->getCurrentDirectory() . '/Tables/' . $caseName . '/**/*.yml'));
$connection->rollBack();
}
4.
API , , .. . / , , ( , ).
:
, , . , - , .
dbunit, . , .
public function tearDown(): void
{
parent::tearDown();
// DB-
//
self::$onSetUpCommandArray = [];
}
public static function tearDownAfterClass(): void
{
parent::tearDownAfterClass();
self::$onSetUpCommandArray = [
Client::COMMAND_TRUNCATE,
Client::COMMAND_INSERT
];
}
5.
— , . , . , .
pipeline’, .
pipeline’ ( testsuite phpunit). .
<testsuite name="functional-v2">
<directory>./../../tests/Functional/Controller/Version2</directory>
</testsuite>
functional-v2:
extends: .template_test
services:
- name: percona:5.7
script:
- sh ./scripts/ci/migrations_dump_load.sh
- ./vendor/phpunit/phpunit/phpunit --testsuite functional-v2 --configuration config/test/phpunit.ci.v2.xml --verbose
, , , paratest. .
, .. . , ( ), , .. - .
:
CI —
, -
, - ( , ) . CI, .
...
6.
, . . , , . - bootstrap , .
( ). , , , .. DI- (, - , ..).
, , . , .
interface StateResetInterface
{
public function resetState();
}
$container = self::$app->getContainer();
foreach ($container->getKnownEntryNames() as $dependency) {
$service = $container->get($dependency);
if ($service instanceof StateResetInterface) {
$service->resetState();
}
}
テストの作成は、実際のアプリケーション自体の作成と常に同じ妥協点です。あなたにとってより優先され、何を寄付できるかという事実から先に進む必要があります。「理想的な」テストについて一方的に言われることがよくありますが、実際には実装が難しい、作業が遅い、サポートに手間がかかる可能性があります。
すべての最適化の後、機能テストのCIでの実行時間は12〜15分に短縮されました。もちろん、上記のテクニックが元の形で役立つかどうかは疑問ですが、それらがインスピレーションを得て、私自身のアイデアを与えてくれたことを願っています!
テストを書くためにどのようなアプローチを使用していますか?それらを最適化する必要があり、どのような方法を使用しましたか?