如何对编码器进行单元测试?


9

我有这样的事情:

public byte[] EncodeMyObject(MyObject obj)

我一直在这样的单元测试:

byte[] expectedResults = new byte[3]{ 0x01, 0x02, 0xFF };
Assert.IsEqual(expectedResults, EncodeMyObject(myObject));

编辑:我见过的两种建议是:

1)使用硬编码的期望值,如上面的示例。

2)使用解码器解码编码的字节数组并比较输入/输出对象。

我在方法1中看到的问题是它非常脆弱,并且需要很多硬编码值。

方法2的问题在于测试编码器取决于解码器是否正常工作。如果编码器/解码器在同一位置均被损坏,则测试可能会产生误报。

这些可能是测试此类方法的唯一方法。如果是这样,那很好。我在问这个问题,看看是否有针对这种测试的更好的策略。我无法透露我正在使用的特定编码器的内部。我通常问的是您将如何解决这类问题,我不认为内部问题很重要。假定给定的输入对象将始终产生相同的输出字节数组。


4
如何myObject从去myObject{ 0x01, 0x02, 0xFF }?可以分解并测试该算法吗?我要问的原因是,目前看来,您已经进行了一项测试,可以证明一个神奇的事物会产生另一个神奇的事物。您唯一的信心就是一个输入会产生一个输出。如果您可以分解算法,则可以对算法有更大的信心,并减少对神奇输入和输出的依赖。
安东尼·佩格拉姆

3
@Codism如果编码器和解码器在同一位置损坏怎么办?
ConditionRacer

2
根据定义,测试是在做某事,然后检查您是否获得了预期的结果,这就是测试的结果。当然,您需要确保进行足够的测试,以确保您使用所有代码并涵盖了极端情况和其他怪异之处。
Blrfl 2013年

1
@ Justin984,好,现在我们要深入。我不会将那些私有内部公开为Encoder API的成员,当然不会。我会从编码器中完全删除它们。或者说,编码器将关闭委派到别的地方,一个依赖。如果是无法测试的Monster方法或一堆帮助程序类之间的争斗,那么我每次都会选择帮助程序类。但是,由于我看不到这一点,因此我在此时对您的代码进行了不知情的推断。但是,如果您想对测试充满信心,那么采用较小的方法完成较少的工作就是达到目标的一种方法。
Anthony Pegram

1
@ Justin984如果规格更改,您将更改测试中的预期输出,但现在将失败。然后,您更改编码器逻辑以通过。似乎正是TDD应该如何工作,并且只有在应有的时候它才会失败。我不知道这怎么使它变脆。
丹尼尔·卡普兰

Answers:


1

你在那里有点令人讨厌的情况。如果您要编码为静态格式,则第一种方法就是处理方法。如果这只是您自己的格式,那么除了第二种方法外,没有其他人需要解码。但是您实际上并不适合这些类别。

我要做的是尝试按抽象级别分解事物。

所以我将从位级别的东西开始,我会测试类似

bitWriter = new BitWriter();
bitWriter.writeInt(42, bits = 7);
assertEqual( bitWriter.data(), {0x42} )

因此,想法是写者知道如何写出最原始的字段类型,例如int。

更复杂的类型将通过使用和测试类似的东西来实现:

bitWriter = new BitWriter();
writeDate(bitWriter, new Datetime(2001, 10, 4));

bitWriter2 = new BitWriter();
bitWriter2.writeInt(2001, 12)
bitWriter2.writeInt(10, 4)
bitWriter2.writeInt(4, 6)

assertEquals(bitWriter.data(), bitWriter2.data() )

注意,这避免了有关如何打包实际位的任何知识。该测试已通过之前的测试进行了测试,对于该测试,我们将假设它可以正常工作。

然后在下一个抽象层次上

bitWriter = new BitWriter();
encodeObject(bitWriter, myObject);


bitWriter2 = new BitWriter();
bitWriter2.writeInt(42, 32)
writeDate(bitWriter2, new Datetime(2001, 10, 4));
writeVarString(bitWriter2, "alphanumeric");

assertEquals(bitWriter.data(), bitWriter2.data() )

因此,再次,我们不会尝试包含有关如何实际编码varstrings或日期或数字的知识。在此测试中,我们只对encodeObject产生的编码感兴趣。

最终结果是,如果更改了日期格式,则必须修复实际上涉及日期的测试,但是所有其他代码和测试都与日期的实际编码方式无关,并且一旦更新了代码即可这项工作,所有这些测试都将通过。


我喜欢这个。我想这就是其他一些评论者所说的关于将其分成较小的部分的说法。规格更改时,它不能完全避免问题,但可以使其更好。
ConditionRacer

6

依靠。如果编码是完全固定的,每个实现都应该创建完全相同的输出,那么除了验证示例输入是否映射到期望的输出外,检查其他内容没有任何意义。那是最明显的测试,也许也是最容易编写的测试。

如果像MPEG标准那样有空间摆放其他输出(例如,可以将某些运算符应用于输入,但是可以自由权衡编码工作量与输出质量或存储空间),那么最好应用定义输出的解码策略,并验证其与输入相同-或,如果编码有损,则合理地接近原始输入。这很难编程,但是可以保护您免受编码器将来可能进行的任何改进。


2
假设您使用解码器并比较值。如果编码器和解码器都在同一位置中断怎么办?编码器编码不正确,解码器解码不正确,但是输入/输出对象正确,因为该过程两次执行不正确。
ConditionRacer

@ Justin984然后使用所谓的“测试向量”,知道可以精确用于测试编码器和解码器的输入/输出对
棘手怪胎

@ratchet freak这使我回到了用期望值进行测试的地方。很好,那是我目前正在做的,但是有点脆弱,所以我一直在寻找是否有更好的方法。
ConditionRacer

1
除了仔细阅读标准并为每个规则创建测试用例之外,几乎没有办法避免编码器和解码器都包含相同的错误。例如,假设必须将“ ABC”翻译为“ xyz”,但编码器不知道,并且您的解码器也不会理解“ xyz”。手工制作的测试用例不包含“ ABC”序列,因为程序员并不了解该规则,而且使用编码/解码随机字符串的测试也将错误地通过,因为编码器和解码器都忽略了该问题。
user281377

1
为了帮助捕获由于缺少知识而影响自己编写的解码器和编码器的错误,请努力从其他供应商处获取编码器输出;并尝试在第三方解码器上测试编码器的输出。周围没有其他选择。
rwong

3

测试encode(decode(coded_value)) == coded_valuedecode(encode(value)) == value。您可以根据需要为测试提供随机输入。

编码器和解码器仍然有可能以互补的方式损坏,但是除非您对编码标准有概念上的误解,否则这似乎不太可能。对编码器和解码器进行硬编码测试(就像您已经在做的那样)应该避免这种情况。

如果您可以访问已知可行的其他实现,则至少可以使用它来确保您的实现是好的,即使在单元测试中不可能使用它也是如此。


我同意一般来说不太可能出现互补的编码器/解码器错误。在我的特定情况下,编码器/解码器类的代码是由另一个工具根据数据库中的规则生成的。因此,补充错误的确会偶尔发生。
ConditionRacer

怎么会有“互补错误”?这意味着对于编码形式存在外部规范,因此存在外部解码器。
凯文·克莱恩

我不理解您对“外部”一词的使用。但是有一个关于如何编码数据以及解码器的规范。一个互补的错误是,编码器和解码器都以互补的方式工作,但又偏离了规范。我在原始问题下的评论中有一个例子。
ConditionRacer

如果编码器原本应该实现ROT13,但是偶然地实现了ROT14,而解码器也这样做,则解码(encode('a'))=='a',但编码器仍然损坏。对于比这复杂得多的事情,发生这种事情的可能性可能要小得多,但是从理论上讲,它会发生。
Michael Shaw

@MichaelShaw只是一个琐事,ROT13的编码器和解码器是相同的;ROT13是它自己的逆。如果您错误地实现了ROT14,decode(encode(char))则将不相等char(它将等于char+2)。
Tom Marthenal,

2

测试到要求

如果要求仅是“编码到一个字节流,该字节流在解码时会产生等效的对象。”,则只需通过解码来测试编码器。如果同时编写编码器和解码器,则只需一起测试即可。他们不能有“匹配错误”。如果他们一起工作,则测试通过。

如果数据流还有其他要求,则必须通过检查编码数据来测试它们。

如果预定义了编码格式,那么您将必须像预期的那样根据预期结果验证编码数据,或者(更好)获得可以信赖的参考解码器进行验证。使用参考解码器可以消除您误解格式规范的可能性。


1

根据您使用的测试框架和范例,仍然可以像您所说的那样使用Arrange Act Assert模式。

[TestMethod]
public void EncodeMyObject_ForValidInputs_Encodes()
{
    //Arrange object under test
    MyEncoder encoderUnderTest = new MyEncoder();
    MyObject validObject = new MyOjbect();
    //arrange object for condition under test

    //Act
    byte[] actual = encoderUnderTest.EncodeMyObject(myObject);

    //Assert
    byte[] expected = new byte[3]{ 0x01, 0x02, 0xFF };
    Assert.IsEqual(expected, actual);
}

您应该知道需求,EncodeMyObject()并且可以通过排列每个需求并对所需的结果进行硬编码expected(类似于解码器),并可以使用此模式针对每个需求测试有效和无效的条件。

由于期望是硬编码的,因此如果您进行了很大的更改,这些将很脆弱。

您可能可以通过某种参数驱动(例如Pex)来实现自动化,或者如果您正在执行DDD或BDD,请查看gerkin / cucumber


1

您可以决定什么对您很重要。

对您来说,使对象在往返过程中生存下来是很重要的,确切的连线格式不是很重要吗?还是确切的有线格式是编码器和解码器功能的重要组成部分?

如果是前者,则不仅仅是确保对象能够在往返过程中生存。如果编码器和解码器都以完全互补的方式损坏,那么您实际上就不在乎。

如果是后者,则需要测试连线格式是否符合给定输入的期望。这意味着要么直接测试格式,要么使用参考实现。但是,在测试了基础知识之后,您可能会从其他往返测试中获得价值,这应该更容易编写。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.