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

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

Хорошо тестируемый стек конфигурации требует, чтобы:

  1. Юнит-тесты не читали файлы с диска.
  2. Тестируемый код получал конфигурацию программно.
  3. Поведение горячей перезагрузки проверялось через контролируемые триггеры.

Юнит-тесты — инлайновая конфигурация через 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"

Горячая перезагрузка — Phase 2

:::caution Не реализовано в v0.1 / v0.2 Горячая перезагрузка и подписки на изменения — планируемый API Phase 2. В текущих биндингах (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 возвращает неактивный Subscription. Изменяемый источник (MutableDictSource) и атомарный откат при невалидной перезагрузке — часть того же объёма Phase 2.

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

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

Для end-to-end сценариев, где важен порядок слоёв, задай 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

См. также