Skip to content

Commit 0c633ea

Browse files
committed
Add lifecycle documentation
1 parent 6ebb8f4 commit 0c633ea

File tree

3 files changed

+292
-0
lines changed

3 files changed

+292
-0
lines changed

.vitepress/config.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export default defineConfig({
5656
{ text: 'Naming Conventions', link: '/docs/naming-conventions' },
5757
{ text: 'Inline Tests', link: '/docs/inline-tests' },
5858
{ text: 'Data Providers', link: '/docs/data-providers' },
59+
{ text: 'Lifecycle', link: '/docs/lifecycle' },
5960
],
6061
},
6162
{
@@ -108,6 +109,7 @@ export default defineConfig({
108109
{ text: 'Конвенции именования', link: '/ru/docs/naming-conventions' },
109110
{ text: 'Встроенные тесты', link: '/ru/docs/inline-tests' },
110111
{ text: 'Провайдеры данных', link: '/ru/docs/data-providers' },
112+
{ text: 'Жизненный цикл', link: '/ru/docs/lifecycle' },
111113
],
112114
},
113115
{

docs/lifecycle.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Lifecycle
2+
3+
Lifecycle attributes define setup and teardown methods that run automatically at specific points during test execution.
4+
5+
## Class Instantiation
6+
7+
By default, Testo instantiates each test class **once per test case**, not per test. This means:
8+
9+
- Instance properties persist between tests in the same class
10+
- Constructor runs lazily — right before the first non-static method call
11+
- If all methods are static, the class is never instantiated
12+
13+
```php
14+
final class ServiceTest
15+
{
16+
private Client $client;
17+
private int $counter = 0;
18+
19+
public function __construct()
20+
{
21+
// Runs once — natural place for expensive initialization
22+
$this->client = new Client();
23+
}
24+
25+
#[Test]
26+
public function firstTest(): void
27+
{
28+
$this->counter++;
29+
// $this->counter is now 1
30+
}
31+
32+
#[Test]
33+
public function secondTest(): void
34+
{
35+
$this->counter++;
36+
// $this->counter is now 2 — state persists between tests
37+
// $this->client is still the same instance
38+
}
39+
}
40+
```
41+
42+
To control state between tests, use lifecycle attributes described below.
43+
44+
## Attributes
45+
46+
| Attribute | When it runs | How often |
47+
|-----------|--------------|-----------|
48+
| `#[BeforeEach]` | Before each test method | Once per test |
49+
| `#[AfterEach]` | After each test method | Once per test |
50+
| `#[BeforeAll]` | Before all tests in the class | Once per test case |
51+
| `#[AfterAll]` | After all tests in the class | Once per test case |
52+
53+
## Execution Order
54+
55+
```
56+
BeforeAll (once)
57+
├── BeforeEach
58+
│ └── Test 1
59+
│ └── AfterEach
60+
├── BeforeEach
61+
│ └── Test 2
62+
│ └── AfterEach
63+
└── ...
64+
AfterAll (once)
65+
```
66+
67+
## Basic Example
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+
## Priority
115+
116+
When you have multiple methods with the same lifecycle attribute, use `priority` to control execution order:
117+
118+
```php
119+
#[BeforeEach(priority: 100)]
120+
public function initializeConfig(): void
121+
{
122+
// Runs first (highest priority)
123+
}
124+
125+
#[BeforeEach(priority: 50)]
126+
public function initializeLogger(): void
127+
{
128+
// Runs second
129+
}
130+
131+
#[BeforeEach] // priority: 0 (default)
132+
public function initializeService(): void
133+
{
134+
// Runs last
135+
}
136+
```
137+
138+
Higher values execute first. Default priority is `0`.
139+
140+
## Error Handling
141+
142+
- Exception in `BeforeEach` — test is aborted
143+
- Exception in `AfterEach` — captured, but test result is preserved
144+
- Exception in `BeforeAll` — all tests in the class are aborted
145+
- Exception in `AfterAll` — captured after all tests complete

ru/docs/lifecycle.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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

Comments
 (0)