---
title: "Инициализация в конструкторе"
type: "test-smell"
slug: "constructor-initialization"
url: "http://localhost:3000/ru/test-smells/constructor-initialization.md"
category: "Запахи фикстур"
description: "Тестовый класс инициализирует поля фикстуры в конструкторе вместо специального хука подготовки фреймворка (setUp / @BeforeEach / TestInitialize), обходя жизненный цикл теста."
---
# Инициализация в конструкторе

> Тестовый класс инициализирует поля фикстуры в конструкторе вместо специального хука подготовки фреймворка (setUp / @BeforeEach / TestInitialize), обходя жизненный цикл теста.

## Signs and Symptoms

Тестовый класс имеет явный конструктор, который строит систему под тестом, моки или общие поля — работа, которая должна жить в хуке подготовки фреймворка (`setUp()` / `@Before` / `@BeforeEach` в JUnit, `[TestInitialize]` в MSTest, `beforeEach` в Jest/Vitest).

Характерные признаки:

* Тестовый класс объявляет конструктор, присваивающий значения `final`\-полям/полям экземпляра.
* Подготовка фикстуры живёт вне жизненного цикла, которым реально управляет фреймворк, — поэтому `@BeforeEach`/`@AfterEach`, подготовка базового класса и переинициализация на каждый тест молча обходятся или выполняются в запутанном порядке относительно конструктора.
* Асинхронная подготовка невозможна: внутри конструктора нельзя выполнить `await`, поэтому асинхронная работа по фикстуре вынужденно попадает не туда или дублируется.

Каноническая форма для JUnit (из каталога тестовых запахов):

```java
public class TagEncodingTest extends BrambleTestCase {
    private final CryptoComponent crypto;
    private final SecretKey tagKey;

    public TagEncodingTest() {                 // запах: инициализация в конструкторе
        crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
        tagKey = TestUtils.getSecretKey();
    }
}

```

Аналог на JS/TS — фикстура построена один раз на уровне `describe`/модуля (эквивалент конструктора) вместо построения на каждый тест:

```ts
describe('Cart', () => {
  const cart = new Cart();          // построена один раз, протекает состоянием между тестами
  it('adds an item', () => { cart.add(apple); expect(cart.size).toBe(1); });
  it('starts empty', () => { expect(cart.size).toBe(0); }); // падает: видит apple
});

```

## Reasons for the Problem

**Почему это происходит**

* Разработчики, незнакомые с назначением хука подготовки, хватаются за известную им языковую возможность — конструктор — чтобы инициализировать поля.
* Так выглядит чище: `final`\-поля плюс конструктор кажутся идиоматичными, а IDE может даже сгенерировать конструктор за вас.
* Карго-культ из боевых классов, где инициализация в конструкторе _и есть_ правильный приём.

**Почему это вредно**

* **Обходит жизненный цикл теста.** Фреймворки обещают свежее состояние на каждый тест через хук подготовки и пару `@BeforeEach`/`@AfterEach` (или `beforeEach`/`afterEach`). Логика, спрятанная в конструкторе, находится вне этого контракта, поэтому очистка, подготовка базового класса и гарантии порядка могут не сработать так, как ожидается.
* **Утечка состояния и нестабильность.** Во фреймворках, переиспользующих один экземпляр (или когда вы прячете фикстуру в общую переменную/переменную уровня `describe`), мутации одного теста просачиваются в следующий. Тесты проходят или падают в зависимости от порядка выполнения — ложная уверенность и периодические отказы.
* **Нет асинхронной подготовки.** Конструктор не может выполнять `await`. Любая подготовка, которой нужны ввод-вывод, соединение с БД или запущенный сервер, не может чисто жить там; люди обходят это блокирующими вызовами или дублированием кода в каждом тесте.
* **Читаемость/единообразие.** Ревьюеры и инструменты ожидают фикстуры в общепринятом хуке. Разделение инициализации между конструктором и методом подготовки затрудняет поиск реальной фикстуры и облегчает ошибки.

**Оговорка — это зависит от фреймворка.** Это не универсальное правило. В **xUnit.net** конструктор _и есть_ идиоматичная подготовка на каждый тест (в паре с `IDisposable` для очистки), а MSTest даже поставляет противоположное, включаемое по выбору правило (`MSTEST0020`), предпочитающее конструкторы вместо `[TestInitialize]`. Считайте «инициализацию в конструкторе» запахом именно там, где ваш фреймворк предоставляет специальный хук подготовки (JUnit, классический MSTest, NUnit), а вы его обходите.

## Treatment

Перенесите инициализацию полей из конструктора в хук подготовки фреймворка и предпочитайте свежее состояние на каждый тест общим экземплярам.

1. Удалите конструктор тестового класса; объявите поля и присвойте их в хуке подготовки (`@BeforeEach`/`setUp()` для JUnit, `[TestInitialize]` для MSTest, `beforeEach` для Jest/Vitest).
2. Если подготовка асинхронна, это обязательно — хук может выполнить `await`; конструктор не может.
3. В JS/TS стройте фикстуру _внутри_ `beforeEach` (переприсваивая `let`), а не один раз на уровне `describe`/модуля, чтобы каждый тест получал чистый объект.
4. Сохраняйте симметрию очистки: соединяйте `@BeforeEach` с `@AfterEach` (или, в стиле конструкторов xUnit.net, реализуйте `IDisposable`).

До → после (JUnit):

```java
// до
public class TagEncodingTest {
    private final CryptoComponent crypto = new CryptoComponentImpl(...);
}

// после
public class TagEncodingTest {
    private CryptoComponent crypto;

    @BeforeEach
    void setUp() {
        crypto = new CryptoComponentImpl(...);
    }
}

```

До → после (Jest/Vitest):

```ts
describe('Cart', () => {
  let cart: Cart;
  beforeEach(() => { cart = new Cart(); }); // свежая на каждый тест
  it('adds an item', () => { cart.add(apple); expect(cart.size).toBe(1); });
  it('starts empty', () => { expect(cart.size).toBe(0); });
});

```

Если фикстура действительно неизменяема и дорога (и ваш фреймворк это поддерживает), одноразовый хук вроде `@BeforeAll`/`beforeAll` приемлем — но только для по-настоящему доступного только для чтения общего состояния, никогда для объектов, которые тесты изменяют.

## Detected by

- **tsDetect (Test Smell Detector)** `Constructor Initialization` (https://testsmells.org/pages/testsmells.html)
- **MSTest Roslyn analyzers** `MSTEST0019` (https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0019)
