我该如何进行单元测试呢?

时间:2010-12-08 10:45:36

标签: c# unit-testing mocking rhino-mocks stubbing

我需要开发一个相当简单的算法,但我觉得如何最好地为它编写测试。

一般说明:用户需要能够删除计划。计划有与之关联的任务,这些也需要删除(只要它们尚未完成)。

伪代码,算法应如何表现:

   PlanController.DeletePlan(plan)
     =>
     PlanDbRepository.DeletePlan()
      ForEach Task t in plan.Tasks
          If t.Status = Status.Open Then
            TaskDbRepository.DeleteTask(t)
          End If
      End ForEach

据我了解,单元测试不应该触及数据库或通常需要访问任何外部系统,所以我猜我有两个选择:

1)模拟Repository调用,并检查它们是否被称为适当的次数为Asserts

2)为两个存储库类创建存根,手动设置其删除标志,然后验证是否已将相应的对象标记为删除。

在这两种方法中,最大的问题是:我在这里测试到底是什么?这些测试给我的EXTRA价值是多少?

对此的任何见解都将受到高度赞赏。虽然我们使用了RhinoMocks,但这在技术上并没有与任何特定的单元测试框架相关联。但我更喜欢一般的解释,以便我可以正确地解决这个问题。

7 个答案:

答案 0 :(得分:4)

您应该模拟存储库,然后在包含Open和Closed任务的单元测试中构建虚拟计划。然后调用传递此计划的实际方法,最后验证是否使用正确的参数调用DeleteTask方法(仅具有status = Open的任务)。这样,您可以确保方法仅删除与此计划关联的打开任务。另外,请不要忘记(可能在单独的单元测试中)通过断言已经传递的对象上调用了DeletePlan方法来验证计划本身已被删除。

答案 1 :(得分:2)

为了增加Darin的答案,我想告诉你你在测试什么。那里有一些业务逻辑,例如检查状态。

这个单元测试现在看起来有点愚蠢,但是对于你的代码和模型的未来变化呢?此测试对于确保这个看似简单的功能始终保持正常运行是必要的。

答案 2 :(得分:2)

如您所述,您正在测试算法中的逻辑是否按预期运行。你的方法是正确的,但考虑未来 - 未来几个月,这个算法可能需要改变,一个不同的开发人员砍掉它并重做它,错过了一个关键的逻辑。您的单元测试现在将失败,开发人员将收到他们的错误警报。单元测试在开始时很有用,也可以在几周/几个月/几年内完成。

如果您想添加更多内容,请考虑如何处理失败。让你的数据库模拟在delete命令上抛出异常,测试你的算法是否正确处理了这个。

答案 3 :(得分:2)

您的测试提供的额外价值是检查您的代码是否做了正确的事情(在这种情况下,删除计划,删除与计划相关的任何打开的任务,并保留与计划相关的任何已完成的任务)。

假设您已经为Repository类准备了测试(即,当他们调用delete时他们做了正确的事情),那么您需要做的就是检查是否正确调用了delete方法。

你可以写的一些测试是:
删除空计划只会调用DeletePlan吗? 删除包含两个打开任务的计划是否会为这两个任务调用DeleteTask? 删除包含两个已关闭任务的计划是否根本不会调用DeleteTask? 在正确的任务中删除具有一个打开和一个已关闭任务的计划是否会调用DeleteTask一次?

编辑:我会用Darin的答案作为解决问题的方法。

答案 4 :(得分:1)

有趣的是,我发现单元测试有助于将注意力集中在规范上。 为此,让我问这个问题......

如果我有一项包含3项任务的计划:

Plan1 {
 Task1: completed
 Task2: todo
 Task3: todo
}

我打电话给他们删除,计划会发生什么?

Plan1 : ?
Task1: not deleted
Task2: deleted
Task3: deleted

是否删除了plan1,orphaning task1?或者是否标记为删除?。

这是我在单元测试中看到的值的重要部分(虽然它只是4个值中的1个:   1)规格   2)反馈   3)回归   4)粒度

至于如何测试,我根本不会建议模拟。我会考虑一个2部分的方法 第一个看起来像

public void DeletePlan(Plan p)
{ 
  var objectsToDelete = GetDeletedPlanObjects(p);
  DeleteObjects(objectsToDelete);
} 

我不会测试这种方法。 我会测试方法GetDeletedPlanObjects,它无论如何都不会触及数据库,并且允许你发送上述情况的场景....然后我会用www.approvaltests.com断言,但这是另一个故事: - )

快乐测试, Llewellyn

答案 5 :(得分:0)

我不会为此编写单元测试,因为对我来说这不是测试行为而是实现。如果在某些时候您希望机会不删除任务,而是将它们设置为“禁用”或“忽略”状态,则单元测试将失败。如果以这种方式测试所有控制器,则单元测试非常脆弱,需要经常更换。

如果要为此测试业务逻辑并将删除的实现细节留给类本身,请将业务逻辑重构为“TaskRemovalStrategy”。

答案 6 :(得分:0)

IMO你可以围绕摘要PlanRepository编写单元测试,同样的测试也应该有助于测试数据库中的数据完整性。

例如,您可以编写测试 -

void DeletePlanTest()
{
    PlanRepository repo = new PlanDbRepository("connection string");
    repo.CreateNewPlan(); // create plan and populate with tasks
    AssertIsTrue(repo.Plan.OpenTasks.Count == 2); // check tasks are in open state
    repo.DeletePlan();
    AssertIsTrue(repo.Plan.OpenTasks.Count == 0);
}

即使您的存储库删除了计划并且您的数据库通过级联删除触发器删除了相关任务,此测试仍然有效。

此类测试的价值在于测试是针对PlanDbRepository还是MockRepository运行,它仍会检查行为是否正确。因此,当您更改任何存储库代码甚至数据库架构时,您可以运行测试以检查是否有任何损坏。

您可以创建此类测试,涵盖存储库的所有可能行为,然后使用它们来确保您的任何更改都不会破坏实现。

您还可以使用具体的存储库实例参数化此测试,并在测试存储库的任何未来实现中重用它们。