C# + NUnit:使用字节数组参数进行单元测试方法

6
我想为一些具有字节数组参数的方法编写单元测试。总共大约有100个方法,数组大小从5-10到几百个字节不等。如何生成和存储测试数组?
1. 我应该手动生成它们还是使用某些生成器代码(这些代码也应该进行单元测试)来生成它们? 2. 我应该在测试期间在内存中生成它们,还是应该提前生成并将它们存储在某个地方? 3. 在后一种情况下,我应该将它们存储在文件中(即使单元测试不应该触摸文件系统),还是应该将它们存储在测试代码本身中(例如,在十六进制格式的字符串中,如此: "47 08 00 14 等")?
我开始手动创建它们并将它们存储在测试代码中的十六进制字符串中。我经常使用这样的二进制字符串,所以我可以相对容易地阅读它们(“我甚至看不到代码。 我只看到金发,深褐色头发,红发。”)。问题是,这种方法很慢,我认为使用自动生成器会导致更易维护的测试。但是我该如何测试生成器的输出是否正确?听起来像一个Catch-22...

2
但是我该如何测试生成器的输出是否正确呢?听起来像个进退两难的局面...而且你怎么能够测试你的测试确实在测试它们需要测试的内容呢? :-) 在某个时候,你必须相信和希望 :-) :-) - xanatos
@xanatos 当然可以。但在这种情况下,那个点在哪里?你有什么建议? - kol
这可能是一个愚蠢的问题,但是为什么对这样一个生成器进行单元测试会有问题呢? - Halvard
你能否展示并解释其中的一种方法吗?这可能会更清楚地说明你想要实现什么。 - Halvard
@Halvard 从技术上讲,这些是二进制序列化和反序列化方法。在这些方法使用的二进制格式中,布尔值通常占用1位;整数可以具有任意大小,不仅限于8的倍数;字符串可以以长度为先或以零结尾的格式存储,等等。一些字节是指向其他字段的指针,保存着相对于字节数组开头的偏移量。 - kol
显示剩余3条评论
4个回答

6

我假设你想要这些字节实际上代表可反序列化的对象,而不仅仅是随机的。

大约一百个字节就可以生成一个小的base64编码字符串。你可以将测试输入保存为base64编码的字符串,然后测试可以获取正确的字符串并将其转换为字节。

    const string someScenario = 
"R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==";

    byte[] bytes = Convert.FromBase64String(someScenario);

您可以通过序列化程序提前确定字符串的内容,例如:
public string SerializeAsBase64()
{
    var session = new SessionCredentials { SessionKey = Guid.NewGuid() };

    using (var mem = new MemoryStream())
    {
        var formatter = new BinaryFormatter();
        formatter.Serialize(mem, session);

        var bytes = mem.ToArray();

        return Convert.ToBase64String(bytes);
    }
}

为了确保它能正常工作,你是否也有反序列化器?需要对原始数据进行序列化和反序列化,然后比较两者是否相等。


+1 感谢您的工作,但我认为我的十六进制字符串比它们的base64编码更易读且更易修改。 - kol

3
我建议使用微软的文本模板作为单元测试生成器。只需将.tt文件添加到您的项目中并实现生成器即可。生成是在设计时完成的(当您保存或更改模板时),并在您的项目中生成一个.cs文件。然后您的测试代码可以像普通“手写”代码一样处理,每个字节数组和方法对应一个测试。
生成器还可以加载定义文件以获取字节数组和期望输出。但由于它是在设计时完成的,因此您的单元测试不使用文件系统。
整个T4系统真的值得一看:使用T4文本模板进行设计时代码生成,而且它默认是VisualStudio的一部分 - 无需安装任何东西或更改构建脚本。

谢谢,听起来是个有趣的想法。我以前从没用过TT,所以不确定它们是否有帮助,但我肯定会尝试一下。 - kol

1
你可以使用生成器代码来获取每个测试的byte[]。例如,你可以在Test类中有一个单独的方法,用于生成具有随机大小的byte[]。像这样:
byte[] GetRandomBytes()
{ 
Random random = new Random();
int randomNumber = random.Next(0, 100);
List<byte> bytes = new List<byte>();
for(int b = 0; b < randomNumber; b++)
    bytes.Add(b);
return bytes.ToArray();
}

生成器代码也将通过您的实际测试进行测试,因为它还应处理无效输入情况(我会认为)。

谢谢,但正如我在上面的评论中所说,这些数组具有预定义的结构。 - kol
这是一个非常糟糕的想法,因为它是不确定性的,这意味着测试有时可能会失败,有时可能会通过。 - Tempestas Ludi

1
我建议几种方法,但不确定哪一种适合你或者在你的情况下是否可行,因为没有你的方法示例。如果您能提供一个例子并解释其工作原理,那将是有用的。你的100多个方法都做什么?

1)尝试重构和提取处理字节数组的逻辑到几个帮助方法中。这样,帮助方法可以处理字节数组,然后将反序列化的对象传递给100多个方法,这些方法现在将开始使用对象进行处理。

或者

2)就我个人而言,我更喜欢将字节数组放在测试方法旁边的代码中,这是您目前采取的方法。也许可以将一些输入组合作为函数属性(使用NUnit RowTest扩展)。这使得单元测试相对独立于其他测试。如果您更改参数,则只应影响特定的单元测试。
生成器的最大问题在于,它可能会变得非常复杂,需要为每个100多种方法返回字节数组的逻辑。对生成器的更改可能会影响所有单元测试。

单元测试应该易于维护和独立。通过引入生成器,您正在增加依赖性并增加复杂性。

干杯!


1
+1 提到 RowTest 扩展!我会尝试编写一个类似于 TestCaseAttribute (RowTest 的后继者)的 NUnit 属性,但它能够接受我的十六进制字符串并将其转换为 byte[] - kol

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