如何测试应用程序的正确编码(例如UTF-8)

21
编码问题是我在开发过程中最常遇到的问题之一。每个平台都坚持使用自己的编码方式,很可能是一些非UTF-8的默认设置。 (我通常在Linux上工作,使用UTF-8作为默认设置,我的同事大多在使用德语Windows,使用ISO-8859-1或某些类似的Windows代码页作为默认设置)
我认为,UTF-8是开发国际化应用程序的合适标准。然而,根据我的经验,编码错误通常是在开发后期才被发现的(即使我位于德国,我们有一些特殊字符,与ISO-8859-1一起提供一些可检测的差异)。
我相信,那些完全使用非ASCII字符集(或者知道使用这种字符集的语言)的开发人员在提供测试数据方面会更有优势。但是,对于我们其他人来说,必须有一种方法来简化这个过程。
这里的人们使用什么[技术|工具|激励措施]?如何让你的共同开发人员关注这些问题?如何测试符合性?这些测试是手动进行还是自动进行的?
提前添加一个可能的答案:
最近我发现了fliptitle.com(他们提供了一种简单的方法来编写奇怪的字符“uʍop ǝpısdn” *),我计划使用它们来提供易于验证的UTF-8字符字符串(因为大多数在那里使用的字符都在某些奇怪的二进制编码位置),但肯定还有更系统化的测试、模式或技术来确保UTF-8的兼容性/使用。
注意:尽管已经有了一个被接受的答案,如果还有其他技术和模式,请添加更多答案。虽然只能选择一个答案作为接受答案,但选择正则表达式答案是因为这是解决问题的最少预期角度,尽管也有选择其他答案的理由。感谢您的参与。
*) 对于那些由于字体问题无法看到这些字符的人来说,“倒立”写成“倒立”。

非常感谢到目前为止提供的答案 - 我想保持这个问题一段时间,以尽可能多地积累解决问题的想法。 - Olaf Kock
5个回答

18

感谢您使用 fliptitle

我也在尝试制定一个适当的测试计划,以确保应用程序在整个系统中支持Unicode字符串。

我是双语人士,但两种语言都只使用ISO-8859-1编码。因此,我一直在努力确定测试全面的Unicode可能性的“现实”和“有意义”的方法。

我刚刚发现了这个网址:


后续帖子:

在为我的应用程序设计一些测试之后,我意识到我已经列出了一小部分编码值,这可能对其他人有所帮助。

我在我的测试中使用了以下国际化字符串:

(注意:下面是一些UTF-8编码的文本...希望您可以在浏览器中看到它们)

ユーザー別サイト
简体中文
크로스 플랫폼으로
מדורים מבוקשים
أفضل البحوث
Σὲ γνωρίζω ἀπὸ
Десятую Международную
แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช
∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i)
français langue étrangère
mañana olé

(UTF-8外文/非英语文本结束)

然而,在测试的各个阶段,我意识到仅了解字符串在其各自的外语字母表中呈现时的外观是不足够的。我还需要知道正确的Unicode代码点数字,并且至少需要知道这些字符串在两种编码(UCS-2和UTF-8)中的正确十六进制值。

以下是相应的代码点编号和十六进制值:

str = L"\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"; // JAPAN 
// Little endian UTF-16/UCS-2: e6 30 fc 30 b6 30 fc 30 25 52 b5 30 a4 30 c8 30 00 00
// Hex of UTF-8: e3 83 a6 e3 83 bc e3 82 b6 e3 83 bc e5 88 a5 e3 82 b5 e3 82 a4 e3 83 88 00 

str = L"\u7B80\u4F53\u4E2D\u6587"; // CHINA 
// Little endian UTF-16/UCS-2: 80 7b 53 4f 2d 4e 87 65 00 00 
// Hex of UTF-8: e7 ae 80 e4 bd 93 e4 b8 ad e6 96 87 00

str = L"\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"; // KOREA 
// Little endian UTF-16/UCS-2: 6c d0 5c b8 a4 c2 20 00 0c d5 ab b7 fc d3 3c c7 5c b8 00 00
// Hex of UTF-8: ed 81 ac eb a1 9c ec 8a a4 20 ed 94 8c eb 9e ab ed 8f bc ec 9c bc eb a1 9c 00 

str = L"\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"; // ISRAEL 
// Little endian UTF-16/UCS-2: de 05 d3 05 d5 05 e8 05 d9 05 dd 05 20 00 de 05 d1 05 d5 05 e7 05 e9 05 d9 05 dd 05 00 00
// Hex of UTF-8: d7 9e d7 93 d7 95 d7 a8 d7 99 d7 9d 20 d7 9e d7 91 d7 95 d7 a7 d7 a9 d7 99 d7 9d 00

str = L"\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"; // EGYPT 
// Little endian UTF-16/UCS-2: 23 06 41 06 36 06 44 06 20 00 27 06 44 06 28 06 2d 06 48 06 2b 06 00 00
// Hex of UTF-8: d8 a3 d9 81 d8 b6 d9 84 20 d8 a7 d9 84 d8 a8 d8 ad d9 88 d8 ab 00 

str = L"\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"; // GREECE 
// Little endian UTF-16/UCS-2: a3 03 72 1f 20 00 b3 03 bd 03 c9 03 c1 03 af 03 b6 03 c9 03 20 00 00
// Hex of UTF-8: ce a3 e1 bd b2 20 ce b3 ce bd cf 89 cf 81 ce af ce b6 cf 89 20 e1 bc 80 cf 80 e1 bd b8 00 

str = L"\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"; // RUSSIA 
// Little endian UTF-16/UCS-2: 14 04 35 04 41 04 4f 04 42 04 43 04 4e 04 20 00 1c 04 35 04 36 04 34 04 43 04 3d 04 30 04 40 04 3e 04 34 04 3d 04 43 04 4e 04 00 00
// Hex of UTF-8: d0 94 d0 b5 d1 81 d1 8f d1 82 d1 83 d1 8e 20 d0 9c d0 b5 d0 b6 d0 b4 d1 83 d0 bd d0 b0 d1 80 d0 be d0 b4 d0 bd d1 83 d1 8e 00

str = L"\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"; // THAILAND
// Little endian UTF-16/UCS-2: 41 0e 1c 0e 48 0e 19 0e 14 0e 34 0e 19 0e 2e 0e 31 0e 48 0e 19 0e 40 0e 2a 0e 37 0e 48 0e 2d 0e 21 0e 42 0e 17 0e 23 0e 21 0e 41 0e 2a 0e 19 0e 2a 0e 31 0e 07 0e 40 0e 27 0e 0a 0e 00 00
// Hex of UTF-8: e0 b9 81 e0 b8 9c e0 b9 88 e0 b8 99 e0 b8 94 e0 b8 b4 e0 b8 99 e0 b8 ae e0 b8 b1 e0 b9 88 e0 b8 99 e0 b9 80 e0 b8 aa e0 b8 b7 e0 b9 88 e0 b8 ad e0 b8 a1 e0 b9 82 e0 b8 97 e0 b8 a3 e0 b8 a1 e0 b9 81 e0 b8 aa e0 b8 99 e0 b8 aa e0 b8 b1 e0 b8 87 e0 b9 80 e0 b8 a7 e0 b8 8a 00

str = L"\u222E E\u22C5da = Q,  n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"; // MATHEMATICS 
// Little endian UTF-16/UCS-2: 2e 22 20 00 45 00 c5 22 64 00 61 00 20 00 3d 00 20 00 51 00 2c 00 20 00 20 00 6e 00 20 00 92 21 20 00 1e 22 2c 00 20 00 11 22 20 00 66 00 28 00 69 00 29 00 20 00 3d 00 20 00 0f 22 20 00 67 00 28 00 69 00 29 00 00 00
// Hex of UTF-8: e2 88 ae 20 45 e2 8b 85 64 61 20 3d 20 51 2c 20 20 6e 20 e2 86 92 20 e2 88 9e 2c 20 e2 88 91 20 66 28 69 29 20 3d 20 e2 88 8f 20 67 28 69 29 00 

str = L"fran\u00E7ais langue \u00E9trang\u00E8re"; // FRANCE
// Little endian UTF-16/UCS-2: 66 00 72 00 61 00 6e 00 e7 00 61 00 69 00 73 00 20 00 6c 00 61 00 6e 00 67 00 75 00 65 00 20 00 e9 00 74 00 72 00 61 00 6e 00 67 00 e8 00 72 00 65 00 00 00
// Hex of UTF-8: 66 72 61 6e c3 a7 61 69 73 20 6c 61 6e 67 75 65 20 c3 a9 74 72 61 6e 67 c3 a8 72 65 00

str = L"ma\u00F1ana ol\u00E9"; // SPAIN
// Little endian UTF-16/UCS-2: 6d 00 61 00 f1 00 61 00 6e 00 61 00 20 00 6f 00 6c 00 e9 00 00 00
// Hex of UTF-8: 6d 61 c3 b1 61 6e 61 20 6f 6c c3 a9 00

此外,这里有一些常见的“渲染错误”图像展示。即使底层的字节是符合UTF8规范的,在各种编辑器中也可能发生这些错误的渲染。如果您看到这些渲染错误,那可能意味着您正确生成了一个UTF8字符串,但是您的编辑器/查看器正在尝试使用除UTF8之外的某种编码对其进行解释。

渲染示例1

渲染示例2


谢谢您提供这些指针 - 当初发布时我不知道怎么错过了,现在才发现它们。 - Olaf Kock

8

有一个正则表达式用于检测字符串是否为有效的UTF-8编码:

$field =~
  m/\A(
     [\x09\x0A\x0D\x20-\x7E]            # ASCII
   | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
   |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
   | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
   |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
   |  \xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
   | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
   |  \xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
  )*\z/x;

但这并不能确保文本实际上是UTF-8编码。
举个例子:字母ö(U+00F6)的字节序列和相应的UTF-8序列是0xC3B6。
因此,当您得到0xC3B6作为输入时,可以说它是有效的UTF-8。但是,您无法确定字母ö是否已经被提交。
这是因为想象一下,如果使用的不是UTF-8而是ISO 8859-1,那么序列0xC3B6分别代表字符Ã(0xC3)和¶(0xB6)。
因此,序列0xC3B6既可以使用UTF-8表示ö,也可以使用ISO 8859-1表示ö(尽管后者相当不寻常)。
所以最终只能猜测。

1
哇 - 这是解决问题最不期而遇的角度。我印象深刻。此外,¶ 是最容易被检测为编码错误的字符之一。 - Olaf Kock
尝试在C++中使用上述内容,但某些东西没有起作用--出现了以下内容-- "^([\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$" - serup

3
字符编码中的真正麻烦制造者往往是多个与编码相关的bug以及由于其他bug引入的一些不正确行为。我已经无法数清这种情况发生了多少次。
目标始终是在每个地方正确处理它。所以大多数时候简单的单元测试就能达到目的,甚至不必使用非常复杂的字符集。我通过测试我们国家的字符“ø”来发现所有的bug,因为它在UTF-8和大多数其他字符集中映射不同。
当所有的部分都正确执行时,聚合就可以正常工作。我知道这听起来很琐碎,但是在涉及字符集问题时,这总是对我奏效的 ;)

这是我们公司的口号 - “只要你做得对 - 问题就会消失”。 :) 您如何确保针对UTF-8中的“ö”的测试不起作用,如果它测试ISO-8859-1中的“ö” - 即assertEquals(“ö”,“ö”)变为assertEquals(“ö”,“ö”) - 比喻地说。 - Olaf Kock
1
你可以使用 \u 转义序列或非转义字符来进行断言。 - krosenvold

2
本文涉及IT技术相关内容,主要讨论本地化的难点。我认为您实际上在询问两个问题。其中一个是如何确保所有人正确地处理i8n应用程序,这不是技术问题,而是项目管理问题。如果您想让人们使用通用标准(如UTF-8),则必须强制执行。工具可以帮助,但首先需要告诉人们这样做。除了说UTF-8是我认为可行的方法之外,关于工具的问题很难回答。这实际上取决于您正在进行的项目类型。例如,如果您谈论的是Java项目,则只需正确配置IDE以使用UTF-8编码文件即可。并确保您的UTF-8本地化在外部资源文件中。您可以做的一件事是创建单元测试来检查合规性。如果您的本地化消息/标签在资源文件中,则很容易检查它们是否正确地采用了UTF-8编码。

你说得对 - 这是多个问题同时出现。主要是因为我还没有找到真正解决这个问题的方法(除了“不犯错误”之外...)我正在寻找任何工具,以便在当前和未来的项目中提供帮助。 - Olaf Kock
1
加号 - 你的一个错别字最好地描述了我经历过的情况:“检查起来相当容易……”我喜欢这个,它确实有一些真实性;-) - Olaf Kock

1
在PHP中,我们使用mb_函数,例如mb_detect_encoding()和mb_convert_encoding()。它们并不完美,但它们可以让我们达到99.9%的效果。然后,我们有一些正则表达式来剥离出现时的奇怪字符。
如果您要进行国际化,肯定要使用UTF-8。我们还没有找到将所有数据转换为UTF-8的完美解决方案,我也不确定是否存在这样的解决方案。您只需要不断地尝试调整。

mb_detect_encoding似乎提供了与Gumbo提供的regexp类似但更易读的方法 - 它看起来同样具有启发式,因为ö仍然存在,对吗? 感谢您的输入。 - Olaf Kock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接