方法调用转发的单元测试策略是什么?

时间:2008-09-19 11:17:14

标签: unit-testing

我有以下情况:

public class CarManager
{
  ..

  public long AddCar(Car car)
  {
      try
      {
         string username = _authorizationManager.GetUsername();
         ...
         long id = _carAccessor.AddCar(username, car.Id, car.Name, ....);
         if(id == 0)
         {
             throw new Exception("Car was not added");
         }
         return id;
      } catch (Exception ex) {
         throw new AddCarException(ex);
      }
  }

  public List AddCars(List cars)
  {
     List ids = new List();
     foreach(Car car in cars)
     {
         ids.Add(AddCar(car));
     }
     return ids;
  }
}

我正在嘲笑_reportAccessor,_authorizationManager等。

现在我想对CarManager类进行单元测试。 我应该为AddCar()进行多次测试,例如

AddCarTest()
AddCarTestAuthorizationManagerException()
AddCarTestCarAccessorNoId()
AddCarTestCarAccessorException()

对于AddCars(),我应该重复所有先前的测试,因为AddCars()调用AddCar() - 它似乎重复自己?我可能不会从AddCars()调用AddCar()吗? < P />

请帮忙。

5 个答案:

答案 0 :(得分:2)

这里有两个问题:

  • 单元测试应该一次完成一个测试方法。它们应该被设计成证明您的班级可以完成与系统其他部分集成时的设计工作。因此,您应该模拟出依赖关系,然后为实际使用该类的每种方式编写测试。对于您编写的每个(非平凡)类,将会出现涉及以特定模式调用方法的客户端代码的方案。
  • 调用AddCar的AddCars没有任何问题。您应该重复测试以进行错误处理,但仅限于它有用的目的。单元测试的非正式规则之一是“测试到无聊的点”或(我喜欢这么想)'测​​试直到恐惧消失'。否则你将永远编写测试。因此,如果您确信测试不会添加任何值,则不会编写它。当然你可能错了,在这种情况下你可以稍后回来加入。你不需要第一次完成一个完美的测试,只是你可以建立的坚实基础,因为你更好地了解你的课程需要做的。

答案 1 :(得分:1)

单元测试应仅关注其测试中的相应类。应该模拟不同类型的所有类属性。

假设您有一个类(CarRegistry)使用某种数据访问对象(例如CarPlatesDAO)来加载/存储来自Relational数据库的车牌号。

当您测试CarRegistry时,您不应该关心CarPlateDAO是否正确执行;由于我们的DAO拥有自己的单元测试。

您只需创建行为类似于DAO的模拟,并根据预期行为返回正确或错误的值。如果所有聚合类都是“绿色”,则将此模拟DAO插入CarRegistry并仅测试目标类,而不关心。

Mocking允许分离可测试类并更好地关注特定功能。

答案 2 :(得分:1)

在对AddCar类进行单元测试时,创建将运行每个代码路径的测试。如果_authorizationManager.GetUsername()可以抛出异常,请创建一个测试,此对象的模拟将抛出。 BTW:不要抛出或捕获Exception实例,而是派生一个有意义的Exception类。

对于AddCars方法,您一定应该调用AddCar。但你可能会考虑将AddCar虚拟化并覆盖它,以便测试它是否被列表中的所有汽车调用。

有时你必须改变类设计的可测试性。

答案 3 :(得分:1)

编写探索方法中每个可能方案的测试都是很好的做法。这就是我在项目中进行单元测试的方式。像AddCarTestAuthorizationManagerException()AddCarTestCarAccessorNoId()AddCarTestCarAccessorException()这样的测试可以让您考虑代码失败的所有不同方式,这会导致我找到一种我可能错过的方法的新类型失败以及改善班级的整体设计。

在类似AddCars()调用AddCar()的情况下,我会模拟AddCar()方法并计算AddCars()调用它的次数。我使用的模拟库允许我创建CarManager的模拟并仅模拟AddCar()方法而不模拟AddCars()。然后,您的单元测试可以设置预期AddCar()被调用的次数,您可以根据传入的汽车列表的大小来了解它。

答案 4 :(得分:1)

  

我应该进行多项测试吗?   AddCar(),如

     

AddCarTest()   AddCarTestAuthorizationManagerException()   AddCarTestCarAccessorNoId()   AddCarTestCarAccessorException()

绝对!这会告诉您有价值的信息

  

对于AddCars()我应该重复所有以前的测试,因为AddCars()调用AddCar() - 它似乎   喜欢重复自己?我可能不是从AddCars()调用AddCar()吗?

从AddCars调用AddCar是一个好主意,它避免违反DRY原则。同样,你应该重复测试。可以这样想 - 你已经为AddCar 编写了测试,因此在测试AddCards时,你可以假设AddCar完成了它在上所说的内容。

让我们这样说吧 - 想象一下AddCar是在另一个类中。您将不了解授权管理员。测试AddCars ,不用了解AddCar必须做的事情。

对于AddCars,您需要测试所有正常的边界条件(空列表是否正常工作等)您可能不需要测试AddCar抛出异常的情况,因为您没有尝试捕获它AddCars。