如何处理设置复杂的单元测试并让它们只测试单元

时间:2010-09-02 19:59:39

标签: c# unit-testing

我有一个需要5个参数的方法。此方法用于获取一堆收集的信息并将其发送到我的服务器。

我正在为这种方法编写单元测试,但我遇到了一些麻烦。几个参数是列表<>。需要做一些正确设置的类。我有方法在其他单位(生产代码单位)中正确设置它们。但是,如果我打电话给那些,那么我就打破了单元测试的整个想法(只打了一个“单位”)。

所以....我该怎么办?我是否在我的测试项目中复制了设置这些对象的代码(在辅助方法中),或者我是否开始调用生产代码来设置这些对象?

这是一个假设的例子,试图让这个更清楚:

文件:UserDemographics.cs

class UserDemographics
{
     // A bunch of user demographic here
     // and values that get set as a user gets added to a group.
}

文件:UserGroups.cs

class UserGroups
{
     // A bunch of variables that change based on 
     //  the demographics of the users put into them.
     public AddUserDemographicsToGroup(UserDemographcis userDemographics)
     {}
}

文件:UserSetupEvent.cs

class UserSetupEvent
{
     // An event to record the registering of a user
     // Is highly dependant on UserDemographics and semi dependant on UserGroups
     public SetupUserEvent(List<UserDemographics> userDemographics, 
                           List<UserGroup> userGroups)
     {}
}

文件:Communications.cs

class Communications
{
     public SendUserInfoToServer(SendingEvent sendingEvent, 
                                 List<UserDemographics> userDemographics,
                                 List<UserGroup> userGroups, 
                                 List<UserSetupEvent> userSetupEvents)
     {}
}

所以问题是:要进行单元测试SendUserInfoToServer我应该在我的测试项目中复制SetupUserEventAddUserDemographicsToGroup,还是应该调用它们来帮助我设置一些“真实”参数?

5 个答案:

答案 0 :(得分:1)

您需要测试重复

你是正确的,单元测试不应该调用其他方法,所以你需要“伪造”依赖项。这可以通过以下两种方式之一完成:

  1. 手动编写的测试副本
  2. 嘲讽
  3. 测试重复项允许您将测试中的方法与其依赖项隔离开来。

    我使用Moq进行模拟。您的单元测试应发送“虚拟”参数值,或者可用于测试控制流的静态定义值:

    public class MyTestObject
    {
           public List<Thingie> GetTestThingies()
           {
                 yield return new Thingie() {id = 1};
                 yield return new Thingie() {id = 2};
                 yield return new Thingie() {id = 3};
           } 
    }
    

    如果方法调用任何其他类/方法,请使用mocks(又名“fakes”)。模拟是基于虚拟方法或接口的动态生成对象:

    Mock<IRepository> repMock = new Mock<IRepository>();
    MyPage obj = new MyPage() //let's pretend this is ASP.NET
    obj.IRepository = repMock.Object;
    repMock.Setup(r => r.FindById(1)).Returns(MyTestObject.GetThingies().First());
    var thingie = MyPage.GetThingie(1);
    

    上面的Mock对象使用Setup方法为r => r.FindById(1) lambda中定义的调用返回相同的结果。这称为 expecation 。这允许您在方法中仅测试 代码,而不实际调用任何依赖类。

    一旦你以这种方式设置你的测试,你可以使用Moq的功能来确认一切都按照预期的方式发生:

    //did we get the instance we expected?
    Assert.AreEqual(thingie.Id, MyTestObject.GetThingies().First().Id); 
    //was a method called?
    repMock.Verify(r => r.FindById(1));
    

    Verify方法允许您测试是否调用了方法。通过这些工具,您可以一次将单元测试集中在一个方法上。

答案 1 :(得分:0)

听起来你的单位太紧密了(至少从你的问题的快速查看)。让我好奇的是,例如,您的UserGroups采用UserDemographics,而UserSetupEvent采用UserGroup列表,包括UserDemographics列表(再次)。 List<UserGroup>不应该包含在其构造函数中传递的ÙserDemographics,还是我误解了它?

不知何故,它似​​乎是您的类模型的设计问题,这反过来又使单元测试变得困难。困难的设置程序是指示高耦合的代码气味:)

答案 2 :(得分:0)

引入接口是我更喜欢的。然后你可以模拟使用过的类,你不必重复代码(这违反了不要重复自己的原则),你不必在Communications类的单元测试中使用原始实现。

答案 3 :(得分:0)

你应该使用模拟对象,基本上你的单元测试可能只是生成一些看起来像真实数据的虚假数据而不是调用真实代码,这样你就可以隔离测试并获得可预测的测试结果。

答案 4 :(得分:0)

您可以使用名为NBuilder的工具生成测试数据。它具有非常好的流畅界面,非常易于使用。如果你的测试需要构建列表,那么这个工作会更好。您可以阅读更多相关信息here