Entonces, ¿cómo no sufrir las pruebas funcionales?

Se me pidió que escribiera este artículo al discutir los informes de Heisenbug 2021 en nuestro chat corporativo. Esto se debe al hecho de que se presta mucha atención a la redacción "correcta" de las pruebas. Entre comillas, porque en el papel todo es realmente lógico y razonado, pero en la práctica tales pruebas resultan bastante lentas.





Este artículo está dirigido más bien a principiantes en programación, pero tal vez alguien pueda inspirarse con uno de los enfoques que se describen a continuación.





Creo que todos conocen los principios de las buenas pruebas:





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




Escribir pruebas es siempre el mismo compromiso que escribir la propia aplicación. Es necesario partir del hecho de que para ti es más prioritario, y lo que se puede donar. A menudo se nos habla unilateralmente sobre las pruebas "ideales", que en realidad pueden ser difíciles de implementar, el trabajo es lento o el apoyo requiere mucha mano de obra.





Después de todas las optimizaciones, el tiempo de ejecución en CI para pruebas funcionales ha disminuido a 12-15 minutos. Por supuesto, dudo que las técnicas descritas anteriormente sean útiles en su forma original, ¡pero espero que me hayan inspirado y dado mis propias ideas!





¿Qué enfoque usas para escribir pruebas? ¿Necesita optimizarlos y qué métodos utilizó?








All Articles