Автоматизированное тестирование

Автоматизированное тестирование библиотек форматирования чисел требует учета специфики работы с DOM, событийной моделью браузера и преобразованиями строкового представления чисел. AutoNumeric как библиотека, модифицирующая поведение input-элементов, требует многоуровневого подхода: модульные тесты, интеграционные сценарии и проверка взаимодействия с реальным DOM.


Архитектура тестового покрытия

Тестирование AutoNumeric целесообразно разделять на три слоя:

Модульные тесты

Проверяется изолированная логика:

  • форматирование чисел
  • парсинг строкового ввода
  • применение конфигураций
  • вычисление округления
  • обработка разделителей тысяч и десятичных знаков

Модульный уровень не требует DOM и выполняется в Node.js среде.

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

Проверяется взаимодействие:

  • input-элемент + AutoNumeric
  • события ввода (input, keydown, paste)
  • реакция на изменения значений
  • синхронизация состояния между DOM и внутренней моделью

Используется jsdom.

E2E-поведение (упрощенное)

Проверяется поведение в браузерной среде:

  • корректность форматирования при реальном вводе
  • обработка быстрых последовательностей событий
  • работа с фокусом и blur
  • взаимодействие с формами

Инструменты тестирования

Jest как базовый фреймворк

Jest используется благодаря:

  • встроенному мокингу
  • snapshot testing
  • поддержке jsdom
  • параллельному запуску тестов

Пример базовой конфигурации:

module.exports = {
  testEnvironment: "jsdom",
  setupFilesAfterEnv: ["<rootDir>/tests/setup.js"],
  testMatch: ["**/__tests__/**/*.test.js"]
};

Testing Library и работа с DOM

При интеграционных тестах часто используется @testing-library/dom для имитации пользовательского ввода.

import { getByRole } from "@testing-library/dom";
import AutoNumeric from "autonumeric";

test("форматирование при вводе числа", () => {
  document.body.innerHTML = `<input id="price" />`;

  const input = document.getElementById("price");

  new AutoNumeric(input, {
    decimalPlaces: 2
  });

  input.value = "1234.5";
  input.dispatchEvent(new Event("input"));

  expect(input.value).toBe("1,234.50");
});

Тестирование ядра форматирования

Проверка разделителей

Ключевая логика AutoNumeric связана с форматированием строк:

import { format } from "../src/core/format";

test("разделение тысяч", () => {
  expect(format("1000000")).toBe("1,000,000");
});

Проверка десятичных знаков

test("округление до фиксированного количества знаков", () => {
  expect(format("12.3456", { decimalPlaces: 2 })).toBe("12.35");
});

Тестирование конфигурации

AutoNumeric поддерживает большое количество опций, поэтому тестирование конфигураций выполняется как параметризованные тесты.

Табличные тесты

describe("конфигурации формата", () => {
  const cases = [
    [{ decimalCharacter: "," }, "12,5", "12,5"],
    [{ digitGroupSeparator: " " }, "1000", "1 000"],
    [{ currencySymbol: "$" }, "10", "$10"]
  ];

  test.each(cases)(
    "конфиг %o корректно форматирует %s -> %s",
    (options, input, expected) => {
      expect(format(input, options)).toBe(expected);
    }
  );
});

Тестирование событий ввода

Симуляция input событий

Работа AutoNumeric напрямую зависит от событий браузера:

test("обработка ввода символов", () => {
  document.body.innerHTML = `<input id="num" />`;
  const input = document.getElementById("num");

  new AutoNumeric(input, { decimalPlaces: 0 });

  input.value = "1";
  input.dispatchEvent(new Event("input"));

  input.value = "12";
  input.dispatchEvent(new Event("input"));

  expect(input.value).toBe("12");
});

Тестирование keydown логики

Некоторые символы должны блокироваться:

test("блокировка недопустимых символов", () => {
  document.body.innerHTML = `<input id="num" />`;
  const input = document.getElementById("num");

  new AutoNumeric(input);

  const event = new KeyboardEvent("keydown", { key: "a", cancelable: true });
  const prevented = !input.dispatchEvent(event);

  expect(prevented).toBe(false);
});

Тестирование paste операций

Вставка текста — один из самых проблемных сценариев:

test("обработка вставки некорректного значения", () => {
  document.body.innerHTML = `<input id="num" />`;
  const input = document.getElementById("num");

  new AutoNumeric(input);

  const clipboardEvent = new ClipboardEvent("paste", {
    clipboardData: new DataTransfer()
  });

  clipboardEvent.clipboardData.setData("text", "12abc34");

  input.dispatchEvent(clipboardEvent);

  expect(input.value).toBe("12,34");
});

Snapshot-тестирование форматов

Snapshot применяется для сложных конфигураций:

test("snapshot форматирования валюты", () => {
  const result = format("1234567.89", {
    currencySymbol: "€",
    decimalPlaces: 2,
    digitGroupSeparator: "."
  });

  expect(result).toMatchSnapshot();
});

Snapshot полезен при регрессионной проверке визуального формата.


Тестирование внутреннего состояния экземпляра

AutoNumeric хранит внутреннюю модель числа отдельно от DOM.

test("получение числового значения", () => {
  document.body.innerHTML = `<input id="num" />`;
  const input = document.getElementById("num");

  const an = new AutoNumeric(input, {});
  an.set("1234.56");

  expect(an.getNumber()).toBe(1234.56);
});

Обработка граничных случаев

Пустые значения

test("пустое значение не ломает форматирование", () => {
  expect(format("")).toBe("");
});

Нули и отрицательные числа

test("отрицательные значения", () => {
  expect(format("-0.5")).toBe("-0.50");
});

Очень большие числа

test("обработка больших чисел", () => {
  expect(format("1000000000000")).toBe("1,000,000,000,000");
});

Моки и изоляция DOM

Для стабильных тестов используется изоляция окружения:

beforeEach(() => {
  document.body.innerHTML = "";
  jest.clearAllMocks();
});

В сложных случаях мокируются:

  • window.getSelection
  • document.execCommand
  • input.selectionStart / selectionEnd

Тестирование производительности

Для проверки деградации производительности используется измерение времени выполнения:

test("форматирование 10000 значений", () => {
  const start = performance.now();

  for (let i = 0; i < 10000; i++) {
    format(String(i));
  }

  const end = performance.now();

  expect(end - start).toBeLessThan(200);
});

Интеграция с CI/CD

Автоматические тесты обычно запускаются в pipeline:

  • Node.js окружение
  • jsdom для DOM тестов
  • параллельное выполнение Jest
  • сбор coverage report

Типичная команда:

jest --coverage --runInBand

Порог покрытия:

  • statements: 90%+
  • branches: 85%+
  • functions: 90%+

Покрытие регрессий

Регрессионные тесты фиксируют поведение:

  • форматирование валют
  • локализация разделителей
  • поведение при изменении конфигурации на лету
  • обработка пользовательских кастомных правил

Структура тестового репозитория

tests/
  unit/
    format.test.js
    parse.test.js
  integration/
    input-events.test.js
    paste.test.js
  fixtures/
    numbers.json
  setup.js

Стратегия минимизации флаки-тестов

Флаки-тесты возникают из-за асинхронности и DOM-событий. Для их устранения применяются:

  • синхронная модель событий
  • фиксация таймеров через fake timers
  • контроль порядка dispatchEvent
  • очистка DOM между тестами
jest.useFakeTimers();

Проверка локализации

Тестируются разные локали:

test("европейский формат чисел", () => {
  expect(format("1234.56", {
    digitGroupSeparator: ".",
    decimalCharacter: ","
  })).toBe("1.234,56");
});

Тестирование динамического обновления конфигурации

test("изменение конфигурации на лету", () => {
  const input = document.createElement("input");
  document.body.appendChild(input);

  const an = new AutoNumeric(input, { decimalPlaces: 2 });

  an.update({ decimalPlaces: 0 });
  an.set("123.45");

  expect(input.value).toBe("123");
});

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

test("игнорирование мусорного ввода", () => {
  expect(format("abc---12..34")).toBe("12.34");
});

Массовые сценарии тестирования

Генерация случайных тестов:

function randomNumber() {
  return (Math.random() * 1e6).toFixed(4);
}

test("рандомизированное форматирование", () => {
  for (let i = 0; i < 1000; i++) {
    const num = randomNumber();
    expect(() => format(num)).not.toThrow();
  }
});

Тестирование взаимодействия с формами

test("submit формы с AutoNumeric", () => {
  document.body.innerHTML = `
    <form>
      <input id="amount" />
    </form>
  `;

  const input = document.getElementById("amount");
  new AutoNumeric(input);

  input.value = "1000";
  input.form.dispatchEvent(new Event("submit"));

  expect(input.value).toBeDefined();
});