如何在大型和复杂的类中实现单元测试?

Lis*_*rah 18 c# nunit unit-testing moq

我正在财务系统中实施单元测试,涉及多个计算.其中一个方法是通过参数接收具有100个以上属性的对象,并根据此对象的属性计算返回值.为了实现此方法的单元测试,我需要让所有这个对象都填充有效值.

所以......问题:今天这个对象是通过数据库填充的.在我的单元测试中(我正在使用NUnit),我需要避开数据库并创建一个模拟对象,以仅测试方法的返回.如何用这个巨大的对象有效地测试这个方法?我真的需要手动填写它的所有100个属性吗?有没有办法使用Moq自动化这个对象创建(例如)?

obs:我正在为已经创建的系统编写单元测试.目前重写所有架构是不可行的.
太感谢了!

Ser*_*kiy 15

如果这100个值不相关且您只需要其中的一些,那么您有几个选项.

您可以创建新对象(将使用默认值初始化属性,例如null字符串和0整数)并仅分配必需的属性:

 var obj = new HugeObject();
 obj.Foo = 42;
 obj.Bar = "banana";
Run Code Online (Sandbox Code Playgroud)

您还可以使用AutoFixture之类的,它将为对象中的所有属性分配虚拟值:

 var fixture = new Fixture();
 var obj = fixture.Create<HugeObject>();
Run Code Online (Sandbox Code Playgroud)

您可以手动分配所需的属性,也可以使用夹具构建器

 var obj = fixture.Build<HugeObject>()
                  .With(o => o.Foo, 42)
                  .With(o => o.Bar, "banana")
                  .Create();
Run Code Online (Sandbox Code Playgroud)

另一个用于相同目的的有用库是NBuilder


注意:如果所有属性都与您正在测试的功能相关,并且它们应具有特定值,则没有库可以猜测测试所需的值.唯一的方法是手动指定测试值.如果您在每次测试之前设置一些默认值并且只是更改特定测试所需的内容,则可以省去大量工作.即创建辅助方法,该方法将使用预定义的值集创建对象:

 private HugeObject CreateValidInvoice()
 {
     return new HugeObject {
         Foo = 42,
         Bar = "banaba",
         //...
     };
 }
Run Code Online (Sandbox Code Playgroud)

然后在您的测试中覆盖一些字段:

 var obj = CreateValidInvoice();
 obj.Bar = "apple";
 // ...
Run Code Online (Sandbox Code Playgroud)

  • @Lisa如果您需要准确的值,任何库将如何猜测您需要哪些值?您应该手动提供此类值 (4认同)
  • 谢尔盖,我昨天手动实施了一个解决方案+你的建议+ AutoFixture.如果我的问题的答案确实是:"你真的必须手动填充你的对象",我可以尽量减少字段默认值的工作量,并按照你的建议用AutoFixture生成自动值.谢谢! (2认同)

Nko*_*osi 5

考虑到这些限制(错误的代码设计和技术欠债……我在哄骗),手动进行单元测试非常麻烦。在您必须访问实际数据源(而不是生产环境中的数据源)的地方,将需要进行混合集成测试。

潜在药水

  1. 复制数据库并仅填充填充从属复杂类所需的表/数据。希望代码已经足够模块化,以便数据访问应该能够获取并填充复杂的类。

  2. 模拟数据访问,并使其通过备用源(可能是平面文件?csv)导入必要的数据

所有其他代码都可以集中在模拟执行单元测试所需的任何其他依赖项上。

除非剩下的唯一其他选择是手动填充类。

顺便说一句,这到处都有不好的代码味道,但是由于此时无法更改,因此这超出了OP的范围。我建议您向决策者提及这一点。


tra*_*max 5

对于我必须获得大量实际正确数据进行测试的情况,我已将数据序列化为JSON并将其直接放入我的测试类中.原始数据可以从您的数据库中获取,然后序列化.像这样的东西:

[Test]
public void MyTest()
{
    // Arrange
    var data = GetData();

    // Act
    ... test your stuff

    // Assert
    .. verify your results
}


public MyBigViewModel GetData()
{
    return JsonConvert.DeserializeObject<MyBigViewModel>(Data);
}

public const String Data = @"
{
    'SelectedOcc': [29, 26, 27, 2,  1,  28],
    'PossibleOcc': null,
    'SelectedCat': [6,  2,  5,  7,  4,  1,  3,  8],
    'PossibleCat': null,
    'ModelName': 'c',
    'ColumnsHeader': 'Header',
    'RowsHeader': 'Rows'
    // etc. etc.
}";
Run Code Online (Sandbox Code Playgroud)

当您进行大量此类测试时,这可能不是最佳选择,因为以这种格式获取数据需要相当长的时间.但是,这可以为您提供基本数据,您可以在完成序列化后为不同的测试进行修改.

要获得此JSON,您必须单独查询数据库以查找此大对象,将其序列化为JSON JsonConvert.Serialise并将此字符串记录到源代码中 - 这一点相对简单,但需要一些时间,因为您需要手动执行此操作. ..只有一次.

当我必须测试报告呈现并且从数据库获取数据时,我已成功使用此技术,这不是当前测试的关注点.

ps你需要Newtonsoft.Json使用包JsonConvert.DeserializeObject