---
title: "Ресурсный оптимизм"
type: "test-smell"
slug: "resource-optimism"
url: "http://localhost:3000/ru/test-smells/resource-optimism.md"
category: "Запахи фикстур"
description: "Ресурсный оптимизм — это когда тест предполагает, что внешний ресурс (файл, каталог, таблица БД, переменная окружения или сетевой эндпоинт) уже существует и находится в известном состоянии, вместо того чтобы создать и проверить его, из-за чего тест проходит или падает недетерминированно."
---
# Ресурсный оптимизм

> Ресурсный оптимизм — это когда тест предполагает, что внешний ресурс (файл, каталог, таблица БД, переменная окружения или сетевой эндпоинт) уже существует и находится в известном состоянии, вместо того чтобы создать и проверить его, из-за чего тест проходит или падает недетерминированно.

## Signs and Symptoms

Тест трогает нечто _вне себя_ — файл, временный путь, каталог, строку базы данных, переменную окружения или удалённый эндпоинт — и просто верит, что оно уже на месте и правильной формы. Нет ни шага подготовки, который создавал бы ресурс, ни проверки, что он существует, перед его использованием.

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

* По жёстко зашитому пути читают или пишут без предварительного `existsSync`/`mkdir`/`writeFile`: например, `fs.readFileSync('/tmp/app/config.json')`.
* Тест проходит на вашей машине или при первом прогоне, а затем падает на чистом чекауте, в CI, в другом системном каталоге временных файлов или когда набор тестов выполняется в ином порядке либо параллельно.
* Нужный ему ресурс на самом деле создаётся _другим_ тестом (или ручным/только-для-разработки шагом), поэтому тест работает лишь как побочный эффект порядка выполнения.
* Он открывает файл/соединение и проверяет результат, ни разу не проверив наличие ресурса, — отсутствующий ресурс выбрасывает исключение до настоящей проверки или незаметно отдаёт пустые данные, которые всё равно «проходят».

```js
test('parses the config file', () => {
  // оптимистично: предполагает, что /tmp/app/config.json уже существует и корректно сформирован
  const raw = fs.readFileSync('/tmp/app/config.json', 'utf8');
  expect(JSON.parse(raw).port).toBe(8080);
});

```

Каноническая эвристика обнаружения (testsmells.org / tsDetect): тест использует ресурс типа `File`, не вызвав сначала проверку существования/корректности вроде `exists()`, `isFile()` или `notExists()`.

## Reasons for the Problem

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

* Ресурс присутствовал, пока тест писали (фикстурный файл в репозитории, засеянная dev-база, временный файл, оставленный предыдущим шагом), поэтому автор никогда не ощущает его отсутствия.
* Указать на существующий путь или таблицу — это просто меньше кода, чем выделять и засевать их в подготовке, а затем убирать за собой.
* Копипаст из другого теста, который уже опирался на общее, фоновое состояние.

**Чем это вредит**

* **Недетерминизм / флакование.** Результат зависит от состояния окружения, а не от тестируемого кода. ван Дёрсен и др. описывают ровно это: тесты, которые «в один момент работают отлично, а в другой — с треском падают». Чистые CI-раннеры, свежие чекауты, параллельные воркеры и разные системные каталоги для временных файлов — вот где это бьёт.
* **Ложная уверенность.** Зелёный тест может проходить из-за остаточного состояния от предыдущего прогона или предыдущего теста, а не потому, что текущий код корректен, — либо отсутствующий ресурс рано выбрасывает исключение, и значимая проверка вообще не выполняется.
* **Скрытая связанность и зависимость от порядка.** Когда один тест создаёт то, что потребляет другой, у набора тестов появляется невидимый контракт на порядок выполнения, который ломается при перемешивании или шардировании.
* **Трудно воспроизвести и сопровождать.** Падения нельзя воспроизвести локально, потому что они зависят от характерного для конкретной машины фонового состояния, так что отладка идёт медленно, а тест подрывает доверие.

## Treatment

Сделайте так, чтобы каждый тест **владел и управлял** любым ресурсом, которого он касается, и никогда не полагался на фоновое состояние.

1. **Готовьте в подготовке, прибирайте в завершении.** Используйте _Setup External Resource_ — выделяйте и инициализируйте файлы, каталоги, таблицы БД и соединения в `beforeEach`/`beforeAll`, а освобождайте их в `afterEach`/`afterAll`, чтобы следующий прогон начинался с чистого листа.
2. **Создавайте, а не предполагайте.** Запишите файл, засейте таблицу или поднимите стаб-сервер в подготовке. Если вам действительно необходимо использовать уже существующий ресурс, сначала проверьте, что он есть, чтобы отсутствующий ресурс падал громко и с внятным сообщением, а не портил настоящую проверку.
3. **Изолируйте на каждый тест.** Используйте уникальный временный каталог (`fs.mkdtemp(os.tmpdir() + …)`) или свежую схему/пространство имён на каждый тест вместо общего жёстко зашитого пути, чтобы параллельные прогоны и перезапуски не сталкивались.
4. **А ещё лучше — уберите зависимость.** Замените реальный ресурс моком или in-memory фейком (замоканный `fs`, БД в памяти, заглушённый HTTP), чтобы тест был полностью самодостаточным и детерминированным, — именно такое исправление рекомендует testsmells.org.

До → после:

```js
// до — ресурсный оптимизм
test('parses the config file', () => {
  const raw = fs.readFileSync('/tmp/app/config.json', 'utf8');
  expect(JSON.parse(raw).port).toBe(8080);
});

```

```js
// после — тест владеет ресурсом и проверяет его
import { mkdtemp, writeFile, rm, readFile } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';

let dir;
beforeEach(async () => {
  dir = await mkdtemp(path.join(os.tmpdir(), 'cfg-'));
  await writeFile(path.join(dir, 'config.json'), JSON.stringify({ port: 8080 }));
});
afterEach(() => rm(dir, { recursive: true, force: true }));

test('parses the config file', async () => {
  const raw = await readFile(path.join(dir, 'config.json'), 'utf8');
  expect(JSON.parse(raw).port).toBe(8080);
});

```

## Detected by

- **tsDetect** `Resource Optimism` — Resource Optimism (https://testsmells.org/pages/testsmells.html)
