четверг, 11 ноября 2010 г.

CompareString предпочитает осмысленные строки

Это перевод CompareString prefers meaningful strings. Автор: Майкл Каплан.

Ещё одна причина, почему интернациональное тестирование - это не для новичков...

Как это они говорят на despair.com: "When you earnestly believe you can compensate for a lack of skill by doubling your efforts, there's no end to what you can't do".

Хотя это не проблема в этой команде. Но когда другие команды вызывают наши API, им иногда в голову приходит идея тестировать API как часть тестирования их компонента. И, не понимая как эти API работают, они пишут код, который генерирует случайные Unicode строки и передаёт их в CompareString.

CompareString - это API функция, которая была написана для обработки реальных данных, имеющих лингвистический смысл, а не случайно сгенерированной чепухи. И хотя я не утверждаю, что такой процесс не может найти проблемы, я указываю, что это будет проблема не того сорта, что заставит меня плохо спать ночью (к чему приводят подлинные ошибки, влияющие на клиентов)...

Вот пример, что произошло больше года назад на newsgroups:
Я обнаружил, что CompareStringW ведёт себя очень странно на некоторых Unicode строках - у меня получается примерно такое:
(Странно - это относительный термин, особенно в случае, когда вы генерируете случайные строки)
A < B 
B < C 
C < A
или даже:
A < B 
B < A
Это баг?
Да я признаю, что это не очень воодушевляющий результат. Но вы должны понимать, как создают данные для сравнений и что они представляют.

Цель состоит в том, чтобы дать способ сортировки каждой части Unicode BMP (Basic Multilingual Plane) в соответствии с некоторой выбранной локалью. Каждый раз когда кодовая точка не определена полезно в таблице (например, она не определена в Unicode, она не входит в язык/алфавит, которые имеют в Windows полезные данные, либо ей намеренно не задан вес), то она не будет давать полезную языковую информацию.

Другими словами: сортировка случайной чепухи даст вам случайную чепуху :-)
Эти строки являются случайно сгенерированными, поэтому может оказаться, что проблемные строки просто содержат символы, которые либо не используются или зарезервированы в определенной части пространства Unicode (может быть, нечто похожее на использование private-пространства). Так что, может быть, что CompareStringW прекрасно работает для всех реальных строк, с которыми мы когда-либо столкнёмся. Тем не менее, немного тревожно видеть возвращаемые значения CompareStringW, которые явно не верны.
См. выше. Но я всё же разберу эти примеры ниже.
Вот один из них: три вызова CompareStringW вернут CSTR_LESS_THAN:

A = 1B37 1D96 4516 
B = 30FE 4113 67BE 
C = 0747 4443 40E6


Есть ли тут ошибки? Ну пока я вижу, что все три строки являются допустимыми нуль-терминированными UTF-16 строками - они не являются недопустимыми или некорректными.
Ну, в строке A две кодовые точки не входят в Unicode (и поэтому у них нет веса) и иероглиф из расширения Extension A (нет веса до XP, расположен ближе к концу таблицы на XP и выше).

Строка B начинается с отметки повтора Катаканы, которая влияет на символ перед ней и поэтому никогда не появляется в начале строки. Дальше снова идёт символ из Extension A и стандартный иероглиф CJK.

Строка C сделана из буквы алфавита Syriac и двух символов из Extension A.

Итог: все три строки не имеют смысла и ничего полезного из их тестирования получить не удастся.
Ещё пример:

A = 0D42 65F9 
B = 1111 1B4F
Строка A - это символ из Malayalam и один из CJK - два символа, каждый из которых никогда не будет стоять рядом с другим в реальном мире.

Строка B - это символ Hangul и не определённая кодовая точка - снова: недопустимый тест.
CompareStringW вернёт что A < B и B < A, если я передам -1 как длины обеих строк (документация говорит: "если этот параметр отрицателен, то строка предполагается нуль-терминированной, а длина вычисляется автоматически"). Но если я вычислю длину s строк и передам её сам, то всё будет работать верно (A > B и B < A). Однако, вычисление и передача длины строк не поможет в случае выше.
Ну, этот тип ситуации действительно баг - я буду работать над его исправлением в будущих версиях. Просто-напросто есть много таких мест, где если вы передадите неверные данные, то мы странно их обработаем - особенно в случаях -1 vs длина (поскольку они приводят к разным путям выполнения кода).

Случай с -1 не требует прохода по строке от вызывающего. Он вгрызается в строку по одному элементу за раз, пока не будет известен результат - и разные результаты для этого случая - это, технически, баг. Смягчающее обстоятельство в данном моменте является то, что неверный ввод требует выдавать неверный результат...

Итак, это баги. И я буду их исправлять когда-то. Но справедливости ради надо заметить, что недопустимые строки - это краевой случай. Сначала исправляются баги в обработке осмысленных строк, а уж потом - мусорных. Потому что когда настанет день, в который из всех людей, которых я расстраиваю, останутся только тестеры, которые не понимают, что они тестируют - ну, у меня не будет проблем посмотреть в зеркало на себя...

Мораль истории? Если вы хотите проверить CompareString, то делайте это со списком реальных слов - создайте себе такой список в целевом языке. Возьми статью на этом языке и возьмите слов 200 или 500 из неё (уникальных). Или возьмите список из словаря. Или от клиентов. Но только не надо генерировать случайные слова, которые не удовлетворяют ни правилам языка, ни Unicode (подумайте обо всех этих недопустимых символах!). Work to pass appropriate flags that make sense for the application and the API itself. Не передавайте кодовые точки вне стандарта Unicode, если вы хотите получить назад осмысленные результаты.

И, что более важно: понимайте, что же вы делаете/тестируете. Если вам нужно проверить, что API делает с типичными строками в вашей программе, то поймите сначала, является ли это API верным для вызова - это хорошая идея. Но вам не нужно проверять сам API, если только Microsoft не платит вам за это. Само API работает, а вопрос, который должен вас волновать - работает ли оно в ваших сценариях.

В следующий раз я дам хороший пример сценария, когда лучший результат не получается, и вы применяете другой API...

This post brought to you by "ß" (U+00df, a.k.a. LATIN SMALL LETTER SHARP S)
(которая рассматривается как замена "SS" на всех платформах, так что немцы могут использовать таблицу по умолчанию с кучей других языков...)

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.