Прежде чем менять код, нужно зафиксировать его текущее поведение. Мы писали characterization tests — тесты, которые не проверяют «правильность», а фиксируют факт. Подход Майкла Фезерса из «Working Effectively with Legacy Code»:
// tests/characterization/tariff.test.ts
// Характеризационные тесты: фиксируем поведение старого API
import axios from 'axios';
const LEGACY_URL = 'http://legacy-php.internal';
describe('Tariff Calculator — characterization', () => {
// Записываем реальные ответы легаси как "золотой стандарт"
const goldenCases = [
{
name: 'Москва-Владивосток, 20 тонн, стандарт',
input: { from: 'MSK', to: 'VVO', weight_kg: 20000, type: 'standard' },
// Этот ответ получен от legacy — мы не знаем, правильный ли он
// Но мы знаем, что бизнес на него рассчитывает
expected_price_range: { min: 145000, max: 155000 },
},
{
name: 'Москва-Питер, 500 кг, экспресс',
input: { from: 'MSK', to: 'LED', weight_kg: 500, type: 'express' },
expected_price_range: { min: 8500, max: 9500 },
},
{
name: 'Негабаритный груз с перегрузкой',
input: {
from: 'MSK', to: 'NSK', weight_kg: 45000,
type: 'oversized', reload_points: ['KZN'],
},
expected_price_range: { min: 380000, max: 420000 },
},
];
goldenCases.forEach(({ name, input, expected_price_range }) => {
test(`Legacy: ${name}`, async () => {
const resp = await axios.post(`${LEGACY_URL}/api/tariff/calculate`, input);
expect(resp.status).toBe(200);
expect(resp.data.price).toBeGreaterThanOrEqual(expected_price_range.min);
expect(resp.data.price).toBeLessThanOrEqual(expected_price_range.max);
// Сохраняем полный ответ для сравнения с новым сервисом
expect(resp.data).toMatchSnapshot();
});
});
// Тест на shadow mode: запрос идёт к обоим, сравниваем результаты
goldenCases.forEach(({ name, input }) => {
test(`Shadow compare: ${name}`, async () => {
const [legacyResp, newResp] = await Promise.all([
axios.post(`${LEGACY_URL}/api/tariff/calculate`, input),
axios.post('http://new-tariff.internal/api/v2/tariffs/calculate', input),
]);
const diff = Math.abs(legacyResp.data.price - newResp.data.price);
const diffPercent = (diff / legacyResp.data.price) * 100;
// Допускаем расхождение до 0.01% (ошибки округления)
expect(diffPercent).toBeLessThan(0.01);
});
});
});
Оставить комментарий