验证组件是否可以保存/检索UTF8编码字符串的最小测试是什么

时间:2017-04-21 20:21:43

标签: java string encoding utf-8

我正在集成测试组件。该组件允许您保存和获取字符串。

我想验证组件是否正确处理UTF-8字符。验证这一点需要的最低测试是什么?

我认为做这样的事情是一个好的开始:

// This is the ☺ character
String toSave = "\u263A";
int id = 123;

// Saves to Database
myComponent.save( id, toSave );

// Retrieve from Database
String fromComponent = myComponent.retrieve( id );

// Verify they are same 
org.junit.Assert.assertEquals( toSave, fromComponent );

我过去犯过的一个错误是我设置了String toSave = "è"。我的测试通过了,因为字符串已正确保存到数据库并从数据库中正确检索。不幸的是,该应用程序实际上并没有正常工作,因为该应用程序使用的是ISO 8859-1编码。这意味着è工作,但其他像☺的人没有。

重述问题:验证我是否可以保留UTF-8编码字符串的最低测试(或测试)是什么?

3 个答案:

答案 0 :(得分:3)

代码和/或文档审核可能是您最好的选择。但是,您可以根据需要进行探测。似乎足够的测试是目标,最小化它不那么重要。很难根据对威胁的猜测来确定什么是充分的测试,但这是我的建议:所有代码点,包括U + 0000,正确处理"组合字符。 #34;

您要测试的方法有一个Java字符串作为参数。 Java没有" UTF-8编码的字符串&#34 ;:Java的本机文本数据类型使用Unicode字符集的UTF-16编码。这对于Java,.NET,JavaScript,VB6,VBA等使用的text-It的内存表示很常见。 UTF-8通常用于流和存储,因此您应该在"保存和获取"的上下文中询问它。数据库通常提供一个或多个UTF-8,3字节限制的UTF-8或UTF-16(NVARCHAR)数据类型和排序规则。

编码是一个实现细节。如果组件接受Java字符串,它应该为它不愿意处理或正确处理它的数据抛出异常。

"字符"是一个相当不明确的术语。 Unicode代码点的范围从0x0到0x10FFFF-21位。某些代码点未分配(也称为"已定义"),具体取决于Unicode标准版本。 Java数据类型可以处理任何代码点,但有关它们的信息受版本限制。对于Java 8,"Character information is based on the Unicode Standard, version 6.2.0."。您可以将测试限制为"定义"代码点或所有可能的代码点。

码点是基础"字符"或者#34;组合字符"。此外,每个代码点都只有一个Unicode类别。两个类别用于组合字符。要形成字形,基本字符后跟零个或多个组合字符。可能很难以图形方式布置字形(参见Zalgo文本),但对于文本存储,不需要破坏代码点序列(以及字节顺序,如果适用)。

所以,这是一个非最小的,有点全面的测试:

final Stream<Integer> codepoints = IntStream
    .rangeClosed(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT)
    .filter(cp -> Character.isDefined(cp)) // optional filtering
    .boxed();              
final int[] combiningCategories = { 
    Character.COMBINING_SPACING_MARK, 
    Character.ENCLOSING_MARK 
};
final Map<Boolean, List<Integer>> partitionedCodepoints = codepoints
    .collect(Collectors.partitioningBy(cp -> 
        Arrays.binarySearch(combiningCategories, Character.getType(cp)) < 0));
final Integer[] baseCodepoints = partitionedCodepoints.get(true)
    .toArray(new Integer[0]); 
final Integer[] combiningCodepoints = partitionedCodepoints.get(false)
    .toArray(new Integer[0]);
final int baseLength = baseCodepoints.length;
final int combiningLength = combiningCodepoints.length;
final StringBuilder graphemes = new StringBuilder();
for (int i = 0; i < baseLength; i++) {
    graphemes.append(Character.toChars(baseCodepoints[i])); 
    graphemes.append(Character.toChars(combiningCodepoints[i % combiningLength])); 
}
final String test = graphemes.toString();
final byte[] testUTF8 = StandardCharsets.UTF_8.encode(test).array();

// Java 8 counts for when filtering by Character.isDefined 
assertEquals(736681, test.length());  // number of UTF-16 code units
assertEquals(3241399, testUTF8.length); // number of UTF-8 code units

答案 1 :(得分:1)

如果您的组件只能存储和检索字符串,那么您需要做的就是确保在转换为java的Unicode字符串和组件存储的UTF-8字符串时不会丢失任何内容。

这将涉及检查每个 UTF-8代码点长度中的至少一个字符。所以,我建议检查:

  • 来自US-ASCII集的一个字符(1字节长代码点),然后

  • 来自希腊语的一个字符(2字节长代码点)和

  • 中文(3字节长代码点)中的一个字符。

  • 从理论上讲,您还需要使用表情符号(4字节长的代码点)进行检查,尽管这些代码不能用java的Unicode字符串表示,因此它没有实际意义。< / p>

一个有用的额外测试是尝试组合上述每种情况中至少一个字符的字符串,以确保不同代码点长度的字符可以在同一个字符串中共存。

(如果你的组件除了存储和检索字符串之外还做了更多的事情,比如搜索字符串,那么事情可能会变得更复杂,但在我看来你特别避免询问它。)

我确实认为黑盒测试是唯一有意义的测试,因此我不建议使用会暴露其内部知识的方法来污染组件的接口。但是,在不破坏其界面的情况下,可以做两件事来提高组件的可测试性:

  1. 在界面中引入可能有助于测试的其他功能,而不会泄露有关内部实现的任何内容,也不需要测试代码必须了解组件的内部实现。

  2. 介绍在组件的构造函数中进行测试时有用的功能。构造组件的代码确切地知道它正在构造什么组件,因此它非常熟悉组件的性质,所以可以在那里传递特定于实现的东西。

  3. 使用上述任何技术可以做的一个例子是人为地严格限制允许内部表示占用的字节数,这样就可以确保您计划存储的某个字符串会合适。因此,您可以将内部大小限制为不超过9个字节,然后确保正确存储和检索包含3个中文字符的java unicode字符串。

答案 2 :(得分:0)

String个实例使用预定义且不可更改的编码(16位字) 因此,仅从您的服务中返回String可能不足以进行此检查 您应该尝试返回持久化String(例如字节数组)的字节表示,并将此数组的内容与您使用UTF-8字符集以字节为单位编码的"\u263A" String进行比较

String toSave = "\u263A";  
int id = 123;

// Saves to Database
myComponent.save(id, toSave );

// Retrieve from Database
byte[] actualBytes = myComponent.retrieve(id );

// assertion
byte[] expectedBytes = toSave.getBytes(Charset.forName("UTF-8"));
Assert.assertTrue(Arrays.equals(expectedBytes, actualBytes));