|
| 1 | +# Жизненный цикл |
| 2 | + |
| 3 | +Атрибуты жизненного цикла определяют методы setup и teardown, которые автоматически выполняются в определённые моменты во время запуска тестов. |
| 4 | + |
| 5 | +## Инстанциирование класса |
| 6 | + |
| 7 | +По умолчанию Testo создаёт экземпляр тестового класса **один раз на тестовый класс**, а не на каждый тест. Это значит: |
| 8 | + |
| 9 | +- Свойства экземпляра сохраняются между тестами в одном классе |
| 10 | +- Конструктор выполняется лениво — непосредственно перед первым вызовом нестатического метода |
| 11 | +- Если все методы статические, класс не инстанциируется |
| 12 | + |
| 13 | +```php |
| 14 | +final class ServiceTest |
| 15 | +{ |
| 16 | + private Client $client; |
| 17 | + private int $counter = 0; |
| 18 | + |
| 19 | + public function __construct() |
| 20 | + { |
| 21 | + // Выполняется один раз — естественное место для дорогой инициализации |
| 22 | + $this->client = new Client(); |
| 23 | + } |
| 24 | + |
| 25 | + #[Test] |
| 26 | + public function firstTest(): void |
| 27 | + { |
| 28 | + $this->counter++; |
| 29 | + // $this->counter теперь 1 |
| 30 | + } |
| 31 | + |
| 32 | + #[Test] |
| 33 | + public function secondTest(): void |
| 34 | + { |
| 35 | + $this->counter++; |
| 36 | + // $this->counter теперь 2 — состояние сохраняется между тестами |
| 37 | + // $this->client — всё тот же экземпляр |
| 38 | + } |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +Для контроля состояния между тестами используйте атрибуты жизненного цикла, описанные ниже. |
| 43 | + |
| 44 | +## Атрибуты |
| 45 | + |
| 46 | +| Атрибут | Когда выполняется | Как часто | |
| 47 | +|---------|-------------------|-----------| |
| 48 | +| `#[BeforeEach]` | Перед каждым тестовым методом | Один раз на тест | |
| 49 | +| `#[AfterEach]` | После каждого тестового метода | Один раз на тест | |
| 50 | +| `#[BeforeAll]` | Перед всеми тестами в классе | Один раз на тестовый класс | |
| 51 | +| `#[AfterAll]` | После всех тестов в классе | Один раз на тестовый класс | |
| 52 | + |
| 53 | +## Порядок выполнения |
| 54 | + |
| 55 | +``` |
| 56 | +BeforeAll (один раз) |
| 57 | +├── BeforeEach |
| 58 | +│ └── Тест 1 |
| 59 | +│ └── AfterEach |
| 60 | +├── BeforeEach |
| 61 | +│ └── Тест 2 |
| 62 | +│ └── AfterEach |
| 63 | +└── ... |
| 64 | +AfterAll (один раз) |
| 65 | +``` |
| 66 | + |
| 67 | +## Базовый пример |
| 68 | + |
| 69 | +```php |
| 70 | +use Testo\Attribute\Test; |
| 71 | +use Testo\Attribute\BeforeEach; |
| 72 | +use Testo\Attribute\AfterEach; |
| 73 | +use Testo\Attribute\BeforeAll; |
| 74 | +use Testo\Attribute\AfterAll; |
| 75 | + |
| 76 | +final class DatabaseTest |
| 77 | +{ |
| 78 | + private static Connection $connection; |
| 79 | + private Transaction $transaction; |
| 80 | + |
| 81 | + #[BeforeAll] |
| 82 | + public static function connect(): void |
| 83 | + { |
| 84 | + self::$connection = new Connection(); |
| 85 | + } |
| 86 | + |
| 87 | + #[AfterAll] |
| 88 | + public static function disconnect(): void |
| 89 | + { |
| 90 | + self::$connection->close(); |
| 91 | + } |
| 92 | + |
| 93 | + #[BeforeEach] |
| 94 | + public function beginTransaction(): void |
| 95 | + { |
| 96 | + $this->transaction = self::$connection->beginTransaction(); |
| 97 | + } |
| 98 | + |
| 99 | + #[AfterEach] |
| 100 | + public function rollback(): void |
| 101 | + { |
| 102 | + $this->transaction->rollback(); |
| 103 | + } |
| 104 | + |
| 105 | + #[Test] |
| 106 | + public function insertsRecord(): void |
| 107 | + { |
| 108 | + self::$connection->insert('users', ['name' => 'John']); |
| 109 | + Assert::same(1, self::$connection->count('users')); |
| 110 | + } |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +## Приоритет |
| 115 | + |
| 116 | +Когда у вас несколько методов с одинаковым атрибутом жизненного цикла, используйте `priority` для управления порядком выполнения: |
| 117 | + |
| 118 | +```php |
| 119 | +#[BeforeEach(priority: 100)] |
| 120 | +public function initializeConfig(): void |
| 121 | +{ |
| 122 | + // Выполняется первым (наивысший приоритет) |
| 123 | +} |
| 124 | + |
| 125 | +#[BeforeEach(priority: 50)] |
| 126 | +public function initializeLogger(): void |
| 127 | +{ |
| 128 | + // Выполняется вторым |
| 129 | +} |
| 130 | + |
| 131 | +#[BeforeEach] // priority: 0 (по умолчанию) |
| 132 | +public function initializeService(): void |
| 133 | +{ |
| 134 | + // Выполняется последним |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +Большие значения выполняются первыми. Приоритет по умолчанию — `0`. |
| 139 | + |
| 140 | +## Обработка ошибок |
| 141 | + |
| 142 | +- Исключение в `BeforeEach` — тест прерывается |
| 143 | +- Исключение в `AfterEach` — перехватывается, но результат теста сохраняется |
| 144 | +- Исключение в `BeforeAll` — все тесты в классе прерываются |
| 145 | +- Исключение в `AfterAll` — перехватывается после завершения всех тестов |
0 commit comments