我将如何进行单元测试?

发布于 2024-10-06 07:16:02 字数 703 浏览 1 评论 0原文

我需要开发一个相当简单的算法,但我对如何最好地为其编写测试感到有点困惑。

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

伪代码作为算法的行为方式:

   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) 模拟存储库调用,并检查它们是否作为断言被调用了适当的次数

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

在这两种方法中,最大的问题是:我在这里到底要测试什么?此类测试会给我带来什么额外价值?

对此的任何见解都将受到高度赞赏。尽管我们有 RhinoMocks 可供使用,但从技术上讲,这与任何特定的单元测试框架无关。但我更喜欢一个一般性的解释,这样我就可以正确地理解这一点。

I need to develop a fairly simple algorithm, but am kindof confused as how to best write a test for it.

General description: User needs to be able to delete a Plan. Plan has Tasks associated with it, these need to be deleted as well (as long as they're not already done).

Pseudo-code as how the algorithm should behave:

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

Now as far as I understand it, unit tests are not supposed to touch the Database or generally require access to any outside systems, so I'm guessing I have two options here:

1) Mock out the Repository calls, and check whether they have been called the appropriate number of times as Asserts

2) Create stubs for both repository classes, setting their delete flag manually and then verify that the appropriate objects have been marked for deletion.

In both approaches, the big question is: What exactly am I testing here? What is the EXTRA value that such tests would give me?

Any insight in this would be highly appreciated. This is technically not linked to any specific unit testing framework, although we have RhinoMocks to be used. But I'd prefer a general explanation, so that I can properly wrap my head around this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

滥情哥ㄟ 2024-10-13 07:16:02

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

You should mock the repository and then construct a dummy plan in your unit test containing both Open and Closed tasks. Then call the actual method passing this plan and at the end verify that the DeleteTask method was called with correct arguments (tasks with only status = Open). This way you would ensure that only open tasks associated to this plan have been deleted by your method. Also don't forget (probably in a separate unit test) to verify that the plan itself has been deleted by asserting that the DeletePlan method has been called on the object your are passing.

骑趴 2024-10-13 07:16:02

为了补充达林的答案,我想告诉您您实际正在测试的内容。其中有一些业务逻辑,例如状态检查。

这个单元测试现在看起来可能有点愚蠢,但是未来对代码和模型的更改又如何呢?为了确保这个看似简单的功能始终保持工作状态,此测试是必要的。

To add to Darin's answer I'd like to tell you what you are actually testing. There's a bit of business logic in there, for example the check on the status.

This unit test might seem a bit dumb right now, but what about future changes to your code and model? This test is necessary to make sure this seemingly simple functionality will always keep working.

一桥轻雨一伞开 2024-10-13 07:16:02

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

如果您想添加更多,请考虑如何处理失败。让您的数据库模拟在删除命令上抛出异常,测试您的算法是否正确处理此问题。

As you noted, you are testing that the logic in the algorithm behaves as expected. Your approach is correct, but consider the future - Months down the road, this algorithm may need to be changed, a different developer chops it up and redoes it, missing a critical piece of logic. Your unit tests will now fail, and the developer will be alerted to their mistake. Unit testing is useful at the start, and weeks/months/years down the road as well.

If you want to add more, consider how failure is handled. Have your DB mock throw an exception on the delete command, test that your algorithm handles this correctly.

倾城泪 2024-10-13 07:16:02

测试提供的额外价值是检查您的代码是否执行正确的操作(在这种情况下,删除计划,删除与计划关联的任何打开的任务并保留与计划关联的所有已关闭的任务)。

假设您已经对 Repository 类进行了测试(即,当调用它们时,它们会执行正确的操作),那么您需要做的就是检查是否适当地调用了删除方法。

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

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

The extra value provided by your tests is to check that your code does the right things (in this case, delete the plan, delete any open tasks associated with the plan and leave any closed tasks associated with the plan).

Assuming that you have tests in place for your Repository classes (i.e. that they do the right things when delete is called on them), then all you need to do is check that the delete methods are called appropriately.

Some tests you could write are:
Does deleting an empty plan only call DeletePlan?
Does deleting a plan with two open tasks call DeleteTask for both tasks?
Does deleting a plan with two closed tasks not call DeleteTask at all?
Does deleting a plan with one open and one closed task call DeleteTask once on the right task?

Edit: I'd use Darin's answer as the way to go about it though.

你另情深 2024-10-13 07:16:02

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

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

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

并且我对它们调用删除,那么该计划会发生什么?

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

plan1 是否被删除,任务 1 被孤立?或者是否以其他方式标记为已删除?

这是我在单元测试中看到的值的很大一部分(尽管它只是 4 个值中的 1 个:
1) 规格
2)反馈
3)回归
4)粒度

至于如何测试,我根本不建议使用mock。我会考虑两部分方法
第一个看起来像

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

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

快乐测试,
卢埃林

Interesting, I find unit testing helps to focus the mind on the specifications.
To that end let me ask this question...

If I have a plan with 3 tasks:

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

and I call delete on them, what should the happen to the Plan?

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

Is plan1 deleted, orphaning task1? or is it otherwise marked deleted?.

This is a big part of the Value I see in unit tests (Although it is only 1 of the 4 values:
1) Spec
2) Feedback
3) Regression
4) granularity

As for how to test, I wouldn't suggest mocks at all. I would consider a 2 part method
The first would look like

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

And I wouldn't test this method.
I would test the method GetDeletedPlanObjects, which wouldn't touch the database anyways, and would allow you to send in scenarios like the above situation.... which I would then assert with www.approvaltests.com , but that's another story :-)

Happy Testing,
Llewellyn

无边思念无边月 2024-10-13 07:16:02

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

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

I would not write unit tests for this because to me this is not testing behaviour but rather implementation. If at some point you want to chance the behaviour to not delete the tasks but rather set them to a state of 'disabled' or 'ignored', your unit tests will fail. If you test all controllers this way your unit tests are very brittle and will need to be changed often.

Refactor out the business logic to a 'TaskRemovalStrategy' if you want to test the business logic for this and leave the implementation details of the removal up to the class itself.

幸福丶如此 2024-10-13 07:16:02

在我看来,您可以围绕抽象的 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 运行,它仍然会检查行为是否正确。因此,当您更改任何存储库代码甚至数据库架构时,您可以运行测试来检查是否没有损坏。

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

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

IMO you can write your unit tests around the abstract PlanRepository and the same tests should be useful in testing the data integrity in the database also.

For example you could write a test -

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);
}

This test will work even if your repository deletes the plan and your database deletes the related tasks via a cascaded delete trigger.

The value of such test is whether the test is run for PlanDbRepository or a MockRepository it will still check that the behavior is correct. So when you change any repository code or even your database schema, you can run the tests to check nothing is broken.

You can create such tests which cover all the possible behaviors of your repository and then use them to make sure that any of your changes do not break the implementation.

You can also parameterize this test with a concrete repository instance and reuse them the test any future implementations of repositories.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文