Тестирование граничных случаев

Граничные случаи в библиотеке Globalize возникают в ситуациях, когда входные данные находятся на границе допустимых значений либо содержат культурные особенности конкретной локали. Большинство ошибок интернационализации проявляется именно в таких сценариях:

  • пустые значения;
  • null и undefined;
  • некорректные форматы дат;
  • редкие валюты;
  • необычные системы чисел;
  • экстремально большие и маленькие числа;
  • нестандартные правила множественного числа;
  • отсутствие CLDR-данных;
  • неожиданные символы Unicode.

Тестирование таких случаев позволяет избежать ошибок отображения, парсинга и форматирования.


Проверка отсутствующих CLDR-данных

Globalize полностью зависит от CLDR. Если необходимые данные не загружены, библиотека выбрасывает исключения.

Пример ошибки

const Globalize = require("globalize");

Globalize.locale("fr");

const formatter = Globalize.numberFormatter();

console.log(formatter(1234.56));

При отсутствии CLDR:

E_MISSING_CLDR

Тестирование частично загруженных данных

Частая ошибка — загрузка только части модулей CLDR.

Некорректная загрузка

const Cldr = require("cldrjs");

Cldr.load(require("cldr-data/main/en/numbers.json"));

Форматирование даты завершится ошибкой:

const dateFormatter = Globalize.dateFormatter();

dateFormatter(new Date());

Ошибка:

E_MISSING_CLDR

Проверка отсутствующей локали

Сценарий

Globalize.locale("xx-YY");

Несуществующая локаль:

  • может не загрузиться;
  • может использовать fallback;
  • может привести к ошибкам форматирования.

Тестирование fallback-механизмов

Некоторые локали наследуют данные от базовых языков.

Пример

Globalize.locale("en-CA");

Если специфических данных для en-CA нет, используются данные en.

Проверка:

const formatter = Globalize.numberFormatter();

console.log(formatter(12345.67));

Проверка пустых значений

null

const formatter = Globalize.numberFormatter();

formatter(null);

Возможные последствия:

  • исключение;
  • преобразование к 0;
  • строка "null".

Поведение зависит от версии Globalize и пользовательской логики.


undefined

formatter(undefined);

Типичная ошибка:

E_INVALID_PAR_TYPE

Пустая строка

const parser = Globalize.numberParser();

parser("");

Следует проверять:

  • возврат NaN;
  • возврат null;
  • исключение.

Проверка NaN и Infinity

NaN

formatter(NaN);

Во многих локалях отображается как:

NaN

Но отдельные CLDR-наборы могут использовать локализованные представления.


Infinity

formatter(Infinity);

Возможный вывод:


Negative Infinity

formatter(-Infinity);

Проверяется:

  • корректный знак;
  • отсутствие потери Unicode-символов;
  • совместимость шрифтов.

Экстремально большие числа

Большие значения

formatter(999999999999999999999);

Проблемы:

  • потеря точности JavaScript;
  • экспоненциальная запись;
  • округление.

Очень маленькие числа

formatter(0.0000000000000001);

Необходимо проверять:

  • научную нотацию;
  • округление;
  • локальные разделители.

Проверка отрицательных чисел

Разные локали используют разные шаблоны отрицательных значений.

Пример

formatter(-1234.56);

Возможные форматы:

-1,234.56

или

(1,234.56)

Проверка нестандартных систем чисел

Некоторые локали используют альтернативные цифровые системы.

Арабские цифры

Globalize.locale("ar");

const formatter = Globalize.numberFormatter();

console.log(formatter(123456));

Результат:

١٢٣٤٥٦

Проверка Unicode-символов

Локализация часто использует специальные символы Unicode:

  • неразрывные пробелы;
  • RTL-маркеры;
  • специальные разделители групп.

Французская локаль

Globalize.locale("fr");

console.log(formatter(10000));

Результат может содержать:

10 000

Между числами — не обычный пробел.


Проверка bidi-текста

RTL-языки требуют отдельного тестирования.

Пример

Globalize.locale("he");

Проблемы:

  • смешивание RTL и LTR;
  • некорректный порядок валют;
  • разрывы текста.

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

Неизвестная валюта

const formatter = Globalize.currencyFormatter("XXX");

formatter(100);

Проверяется:

  • наличие исключения;
  • fallback;
  • вывод кода валюты.

Проверка устаревших валют

Некоторые валюты больше не используются.

Пример

Globalize.currencyFormatter("DEM");

Следует проверять:

  • наличие CLDR-данных;
  • корректность символа;
  • исторические форматы.

Проверка дробных валют

Не все валюты используют две цифры после запятой.

Японская иена

Globalize.currencyFormatter("JPY")(1000.55);

Результат:

¥1,001

Бахрейнский динар

Globalize.currencyFormatter("BHD")(10.1234);

Валюта использует три знака после запятой.


Проверка округления

Банковское округление

formatter(1.005);

JavaScript может вернуть неожиданный результат:

1.00

Причина — IEEE 754.


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

Пример

const formatter = Globalize.numberFormatter({
    style: "scientific"
});

formatter(123456789);

Проверяется:

  • локализация экспоненты;
  • корректность степени;
  • формат разделителей.

Проверка percent formatter

Граничные значения

const formatter = Globalize.numberFormatter({
    style: "percent"
});

formatter(1);
formatter(0);
formatter(-1);

Проверка дат на границах диапазонов

Минимальная дата

new Date(-8640000000000000)

Максимальная дата

new Date(8640000000000000)

Некоторые движки JavaScript форматируют такие значения по-разному.


Проверка invalid date

Пример

const invalidDate = new Date("invalid");

Проверка:

formatter(invalidDate);

Ожидается:

  • исключение;
  • сообщение об ошибке;
  • отсутствие падения приложения.

Проверка високосных годов

Корректная дата

new Date(2024, 1, 29)

Некорректная дата

new Date(2023, 1, 29)

JavaScript автоматически преобразует:

1 марта 2023

Это необходимо учитывать при тестировании.


Проверка перехода летнего времени

DST — один из самых сложных граничных случаев.

Пример

new Date("2025-03-30T02:30:00")

Во многих временных зонах такой момент времени не существует.


Проверка неоднозначных времён

При переходе на зимнее время один и тот же час встречается дважды.

Пример

new Date("2025-10-26T02:30:00")

Проверка часовых поясов

Нестандартные offset

Некоторые зоны используют:

  • UTC+5:30
  • UTC+5:45
  • UTC+8:45

Проверка календарей

Некоторые локали используют альтернативные календари.

Японский календарь

Globalize.locale("ja");

Исламский календарь

Globalize.locale("ar-SA");

Проверка парсинга чисел

Локализованные разделители

Globalize.locale("de");

const parser = Globalize.numberParser();

parser("1.234,56");

Результат:

1234.56

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

Пример

parser("1,234");

В разных локалях результат отличается:

  • 1234
  • 1.234

Проверка лишних символов

Пример

parser("123abc");

Необходимо тестировать:

  • частичный парсинг;
  • полный отказ;
  • исключение.

Проверка пробелов

Различные типы пробелов

parser("10 000");
parser("10 000");

Вторая строка содержит NBSP.


Проверка zero-width символов

Некоторые данные содержат скрытые Unicode-символы.

Пример

const value = "1\u200B000";

Проверяется устойчивость парсинга.


Проверка plural rules

Правила множественного числа различаются между языками.

Английский

const plural = Globalize.pluralGenerator();

plural(1); // one
plural(2); // other

Русский

Globalize.locale("ru");

Категории:

  • one
  • few
  • many
  • other

Проверка дробных чисел в plural rules

Пример

plural(1.5);

Некоторые языки имеют отдельные категории для дробных значений.


Проверка message formatter

Отсутствующий параметр

const formatter = Globalize.messageFormatter(
    "{name} has {count} messages"
);

formatter({
    count: 5
});

Параметр name отсутствует.


Проверка неверного типа параметра

Пример

formatter({
    count: {}
});

Проверка вложенных plural-конструкций

Сложный ICU-шаблон

{
    count,
    plural,
    one {# file}
    other {# files}
}

Тестируются:

  • вложенность;
  • производительность;
  • корректность парсинга.

Проверка производительности на больших объёмах

Массовое форматирование

for (let i = 0; i < 100000; i++) {
    formatter(i);
}

Проверяются:

  • утечки памяти;
  • деградация скорости;
  • кеширование.

Проверка конкурентного доступа

В серверных приложениях форматтеры могут использоваться одновременно.

Пример

Promise.all([
    asyncTask1(),
    asyncTask2(),
    asyncTask3()
]);

Проверяется:

  • потокобезопасность;
  • мутация глобального состояния;
  • влияние Globalize.locale().

Проверка смены локали во время выполнения

Пример

Globalize.locale("en");

const enFormatter = Globalize.numberFormatter();

Globalize.locale("fr");

const frFormatter = Globalize.numberFormatter();

Важно убедиться, что старые formatter-объекты:

  • не ломаются;
  • сохраняют локаль;
  • не используют новые настройки.

Проверка сериализации formatter-объектов

Форматтеры содержат внутреннее состояние.

Пример

JSON.stringify(formatter);

Проверяется:

  • отсутствие циклических ссылок;
  • корректность сериализации;
  • невозможность случайной утечки данных.

Проверка совместимости версий CLDR

Разные версии CLDR могут изменять:

  • символы валют;
  • правила plural;
  • форматы дат;
  • шаблоны чисел.

Проверка старых браузеров

Особенно важны:

  • Internet Explorer;
  • старые Android WebView;
  • embedded-браузеры.

Проблемы:

  • отсутствие Intl;
  • проблемы Unicode;
  • некорректная работа Date.

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

Пример на Jest

describe("number formatting", () => {
    test("formats large numbers", () => {
        const formatter = Globalize.numberFormatter();

        expect(formatter(1000000))
            .toBe("1,000,000");
    });
});

Параметризованные тесты

Пример

const locales = ["en", "fr", "de", "ru"];

locales.forEach(locale => {
    test(`locale ${locale}`, () => {
        Globalize.locale(locale);

        const formatter = Globalize.numberFormatter();

        expect(formatter(1000)).toBeDefined();
    });
});

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

Подходит для проверки больших наборов локализованных строк.

Пример

expect(formatter(12345.67))
    .toMatchSnapshot();

Проверка регрессий

После обновления CLDR или Globalize необходимо повторно проверять:

  • plural rules;
  • валюты;
  • временные зоны;
  • ICU-шаблоны;
  • форматирование дат.

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

Особенно опасны:

  • смешанные локали;
  • copy-paste из Excel;
  • нестандартные Unicode-символы;
  • мобильные клавиатуры.

Пример

parser("1234");

Используются fullwidth-символы Unicode.


Проверка безопасности

Некорректный ICU-шаблон может вызвать ошибки.

Пример

"{name"

Необходимо тестировать:

  • обработку исключений;
  • защиту от падений;
  • валидацию шаблонов.

Стресс-тестирование

Генерация случайных данных

for (let i = 0; i < 100000; i++) {
    formatter(Math.random() * Number.MAX_VALUE);
}

Помогает выявлять:

  • редкие ошибки;
  • утечки памяти;
  • проблемы округления.

Проверка кеширования

Globalize активно использует внутренние кеши.

Проверка

const formatter1 = Globalize.numberFormatter();
const formatter2 = Globalize.numberFormatter();

Тестируются:

  • повторное использование;
  • производительность;
  • рост памяти.

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

При серверном рендеринге особенно важны:

  • изоляция локалей;
  • отсутствие shared state;
  • корректная работа в Node.js.

Проверка многопоточности в Node.js

При использовании Worker Threads:

const { Worker } = require("worker_threads");

Следует проверять:

  • независимость CLDR-контекста;
  • корректную инициализацию;
  • отсутствие гонок данных.