Então, como você pode não sofrer com os testes funcionais?

Fui solicitado a escrever este artigo discutindo relatórios do Heisenbug 2021 em nosso bate-papo corporativo. Isso se deve ao fato de que se dá muita atenção à escrita "correta" dos testes. Entre aspas - porque no papel tudo é realmente lógico e fundamentado, mas na prática esses testes revelam-se bastante lentos.





Este artigo é direcionado a iniciantes em programação, mas talvez alguém possa se inspirar em uma das abordagens descritas abaixo.





Acho que todos conhecem os princípios de bons testes:





  • , .. (, 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.jsonTables
│   └── 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();
        }
}
      
      




Escrever testes é sempre o mesmo compromisso que escrever o próprio aplicativo. É preciso partir do fato de que pra você é mais prioridade, e o que pode ser doado. Freqüentemente, somos informados unilateralmente sobre os testes “ideais”, que na realidade podem ser difíceis de implementar, o trabalho é lento ou o suporte exige muita mão de obra.





Depois de todas as otimizações, o tempo de execução em CI para testes funcionais diminuiu para 12-15 minutos. Claro, eu duvido que as técnicas descritas acima sejam úteis em sua forma original, mas espero que elas tenham me inspirado e me dado minhas próprias idéias!





Que abordagem você usa para escrever testes? Você precisa otimizá-los e quais métodos você usou?








All Articles