Перейти к основному содержимому

Тестирование

Правильно тестируемая конфигурация требует, чтобы:

  1. Unit-тесты не читали файлы с диска.
  2. Тестируемая логика получала конфиг программно.
  3. Проверка hot-reload — через контролируемые триггеры.

Unit-тесты — inline-конфиг через in-memory источник

Вместо чтения app-config.yaml, собирайте конфиг прямо в тесте. Имя in-memory источника различается между биндингами:

БиндингКласс
dagstack-config (Python)InMemorySource
@dagstack/config (TypeScript)InMemorySource
go.dagstack.dev/config (Go)DictSource (конструктор NewDictSource)
tests/test_database_pool.py
import pytest
from dagstack.config import Config, InMemorySource
from app.database import DatabaseConfig, DatabasePool


def test_pool_uses_configured_size():
config = Config.load_from([
InMemorySource({
"database": {
"host": "localhost",
"port": 5432,
"name": "test",
"user": "app",
"password": "test-pw",
"pool_size": 42,
},
}),
])
pool = DatabasePool(config.get_section("database", DatabaseConfig))

assert pool.size == 42


def test_pool_rejects_invalid_host():
config = Config.load_from([
InMemorySource({
"database": {
"host": "0.0.0.0", # запрещено валидатором
"name": "test",
"user": "app",
"password": "pw",
},
}),
])
with pytest.raises(ValueError, match="host must be a concrete"):
config.get_section("database", DatabaseConfig)

Файловый тест во временном каталоге — YamlFileSource в tmpdir

Когда нужно проверить именно YAML-парсинг (вложенные значения по умолчанию, env-интерполяция), пишите YAML во временный каталог:

import pytest
from pathlib import Path
from dagstack.config import Config


@pytest.fixture
def app_config_yaml(tmp_path: Path, monkeypatch) -> Path:
# Env-переменные для интерполяции:
monkeypatch.setenv("DB_PASSWORD", "test-pw")

yaml_path = tmp_path / "app-config.yaml"
yaml_path.write_text("""
database:
host: "${DB_HOST:-localhost}"
password: "${DB_PASSWORD}"
name: "test_db"
user: "app"
pool_size: 5
""")
return yaml_path


def test_env_interpolation(app_config_yaml):
config = Config.load(str(app_config_yaml))
assert config.get_string("database.password") == "test-pw"
assert config.get_string("database.host") == "localhost"

Hot-reload — Phase 2

:::caution Не реализовано в v0.1 / v0.2 Hot-reload и подписки на изменения — запланированный Phase 2 API. В текущих биндингах (dagstack-config v0.2.0, config-go v0.1.0, @dagstack/config v0.1.0) методы-регистраторы (on_change / on_section_change / reload в Python, OnChange / OnSectionChange / Reload в Go) присутствуют в API, но callback никогда не вызывается: reload() — no-op, on_change возвращает inactive Subscription. Mutable источник (MutableDictSource) и атомарный откат при невалидной перезагрузке — часть того же Phase 2 scope.

Для тестов в v0.1/v0.2 используйте immutable конфиг: собирайте отдельный Config под каждый сценарий через load_from / loadFrom / LoadFrom. :::

Интеграционные тесты с DAGSTACK_ENV

Для e2e-сценариев, где важен порядок слоёв, подставляйте DAGSTACK_ENV:

def test_production_overrides_base(tmp_path, monkeypatch):
(tmp_path / "app-config.yaml").write_text("""
database:
pool_size: 20
host: "localhost"
name: "test"
user: "app"
password: "pw"
""")
(tmp_path / "app-config.production.yaml").write_text("""
database:
pool_size: 100
""")

monkeypatch.setenv("DAGSTACK_ENV", "production")
monkeypatch.chdir(tmp_path)
config = Config.load("app-config.yaml")
assert config.get_int("database.pool_size") == 100

Прогон в CI

.gitea/workflows/config-tests.yml
name: Config tests

on: [push, pull_request]

jobs:
test:
runs-on: dagstack-runner
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- run: pip install -e '.[test]'
- run: pytest tests/ -v

См. также