Jadi bagaimana Anda tidak menderita tes fungsional?

Saya diminta untuk menulis artikel ini dengan membahas laporan dari Heisenbug 2021 di obrolan korporat kami. Hal ini disebabkan oleh fakta bahwa banyak perhatian diberikan pada penulisan tes yang "benar". Dalam tanda kutip - karena di atas kertas semuanya benar-benar logis dan masuk akal, tetapi dalam praktiknya tes semacam itu ternyata agak lambat.





Artikel ini lebih ditujukan untuk pemula dalam pemrograman, tetapi mungkin seseorang dapat terinspirasi oleh salah satu pendekatan yang dijelaskan di bawah ini.





Saya pikir semua orang tahu prinsip tes yang baik:





  • , .. (, HTTP- )





  • , ..





  • , .. CI





, : pipeline 3000 !






, , , .. . .





API :





  1. ( )





  2. ( )





  3. 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 . - , git- .





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.

. , , . :









  1. :





















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();
        }
}
      
      




Menulis tes selalu merupakan kompromi yang sama seperti menulis aplikasi itu sendiri. Perlu untuk melanjutkan dari kenyataan bahwa bagi Anda lebih banyak prioritas, dan apa yang dapat disumbangkan. Kami sering diberi tahu secara sepihak tentang tes "ideal", yang pada kenyataannya sulit untuk diterapkan, pekerjaan lambat, atau dukungan padat karya.





Setelah semua pengoptimalan, waktu berjalan di CI untuk uji fungsional telah menurun menjadi 12-15 menit. Tentu saja, saya ragu bahwa teknik yang dijelaskan di atas akan berguna dalam bentuk aslinya, tetapi saya harap teknik tersebut menginspirasi dan memberi saya ide saya sendiri!





Pendekatan tes menulis apa yang Anda gunakan? Apakah Anda perlu mengoptimalkannya, dan metode apa yang Anda gunakan?








All Articles