ASP.NET MVC - 单元测试是否过度?(TDD)

17

我开始逐渐接受TDD理念,但是我在想自己是否正确地实施了它...我似乎写了很多测试用例。

当然,测试用例越多越好,但我有种感觉我可能过度做了。而且,说实话,我不知道我能坚持多久去编写这些简单重复的测试用例。

例如,这是我的AccountController中LogOn操作的测试用例:

public ActionResult LogOn(string returnUrl)
{
    if (string.IsNullOrEmpty(returnUrl))
        returnUrl = "/";

    var viewModel = new LogOnForm()
    {
        ReturnUrl = returnUrl
    };

    return View("LogOn", viewModel);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(LogOnForm logOnForm)
{
    try
    {
        if (ModelState.IsValid)
        {
            AccountService.LogOnValidate(logOnForm);

            FormsAuth.SignIn(logOnForm.Email, logOnForm.RememberMe);

            return Redirect(logOnForm.ReturnUrl);
        }
    }
    catch (DomainServiceException ex)
    {
        ex.BindToModelState(ModelState);
    }
    catch
    {
        ModelState.AddModelError("*", "There was server error trying to log on, try again. If your problem persists, please contact us.");
    }

    return View("LogOn", logOnForm);
}
相当容易理解。
然后我有以下一组测试。
public void LogOn_Default_ReturnsLogOnView()
public void LogOn_Default_SetsViewDataModel()
public void LogOn_ReturnUrlPassedIn_ViewDataReturnUrlSet()
public void LogOn_ReturnUrlNotPassedIn_ViewDataReturnUrDefaults()
public void LogOnPost_InvalidBinding_ReturnsLogOnViewWithInvalidModelState()
public void LogOnPost_InvalidBinding_DoesntCallAccountServiceLogOnValidate()
public void LogOnPost_ValidBinding_CallsAccountServiceLogOnValidate()
public void LogOnPost_ValidBindingButAccountServiceThrows_ReturnsLogOnViewWithInvalidModelState()
public void LogOnPost_ValidBindingButAccountServiceThrows_DoesntCallFormsAuthServiceSignIn()
public void LogOnPost_ValidBindingAndValidModelButFormsAuthThrows_ReturnsLogOnViewWithInvalidModelState()
public void LogOnPost_ValidBindingAndValidModel_CallsFormsAuthServiceSignIn()
public void LogOnPost_ValidBindingAndValidModel_RedirectsToReturnUrl()

这是否有些过度了?我甚至还没有展示服务测试!

有哪些测试(如果有的话)可以削减掉?

TIA,
Charles

5个回答

17

这完全取决于你需要/想要多少覆盖率以及可靠性有多重要。

以下是你应该问自己的问题:

  • 这个单元测试有助于实现我没有的功能/代码更改吗?
  • 如果我以后进行更改,这个单元测试会帮助回归测试/调试这个单元吗?
  • 为满足这个单元测试的代码是否不平凡或者值得编写一个单元测试?

关于第三个问题,我记得当我开始编写单元测试(我知道,与TDD不同)时,我的测试可能会像这样:

string expected, actual;
TypeUnderTest target = new TypeUnderTest();
target.PropertyToTest = expected;
actual = target.PropertyToTest;
Assert.AreEqual<string>(expected, actual);

我本可以用时间做些更有价值的事情,比如为我的桌面选择更好的壁纸。

我推荐阅读ASP.net MVC书籍作者桑德森的这篇文章:

http://blog.codeville.net/2009/08/24/writing-great-unit-tests-best-and-worst-practises/


是的,听起来回答这些问题应该会随着经验而更容易。有趣的是你提到了Steve,因为他的MVC书目前在我的床头柜上...昨晚我完成了第一章!;-)听起来我应该开始阅读它了。 - Charlino
@Charlino,我刚刚下单了!在阅读了评论和他的一些帖子之后,我很兴奋!希望这可以帮到你。 - TJB

5
我认为你可能做得比必要的多。虽然测试代码可能会走过每一个可能的路径,但有些路径并不是很重要或者不会导致真正的行为差异。
以你的例子为例,LogOn(string returnUrl)。在其中,你首先检查returnUrl参数,并在其为空/空字符串时重新分配默认值。你真的需要一个完整的单元测试来确保这一行代码按预期执行吗?它不太容易出错。
大多数可能破坏该行的更改都会引发编译错误。该行中分配的默认值的更改是可能的(也许您后来会决定“/”不是一个好的默认值...但在您的单元测试中,我敢打赌您已经硬编码了它来检查“/”,对吧?因此,值的更改将需要更改您的测试...这意味着您正在测试数据而不是行为。
您可以通过仅使用一个没有提供参数的测试来实现对方法行为的测试。这将触发您程序中的“设置默认值”部分,同时仍然测试代码的其余部分表现良好。

听起来是非常好的建议 - 我会带回家好好琢磨一下。关于硬编码的“/”测试 - 是的,就像挑出一个鼻屎一样容易!;-) - Charlino
请记住,我并不主张在测试中不硬编码一些值,尽管有些纯粹主义者持不同意见。在这种情况下,我可能会硬编码针对“/”进行测试... 我只是主张你的测试不应该仅仅测试数值。 - Stephen M. Redd

3

我觉得这看起来大致正确。是的,你将编写许多单元测试,并且最初它似乎过度,浪费时间; 但坚持下去,它一定会值得的。你应该追求的是100%功能覆盖率,而不是仅仅100%代码覆盖率。但是,如果你发现为同一个方法编写了许多单元测试,那么可能是该方法做了太多的事情。尝试更好地分离你的职责。在我的经验中,一个操作的主体应该只是新建一个类来执行真正的工作。你应该真正针对这个类进行单元测试。

克里斯


好的,你是说所有这些测试都有用处?关于编写过多的测试和分离你的关注点...我认为我的post方法有太多的测试(如果我这么说的话),而且关注点的分离也刚刚好。我想这真的是一个迹象,表明我正在编写太多的测试了...?我一定会坚持下去,我认为随着经验的增加,我将能够更容易地辨别什么需要测试和什么不需要测试(正如你和许多其他人所暗示的那样)。 - Charlino

2

我只对自己不确定的代码进行单元测试。当然,你永远不知道什么会背叛你,但是为琐碎的事情编写测试似乎对我来说有些过度。

我不是单元测试/TDD大师——但我认为,如果您不是为了拥有它们而编写测试,那么这很好。他们必须有用。如果您已经具有足够的单元测试经验,则可以开始“感觉”何时它们将有价值,何时不会。

您可能会喜欢这本书

编辑:
实际上,在隔离框架章节下面,我找到了一句关于此问题的引语。虽然它是关于过度指定测试(一个特定的测试),但我想这个想法在更全局的范围内仍然存在:

过度指定测试
如果您的测试有太多期望,即使轻微的代码更改也可能导致测试失败,尽管整体功能仍然可用。将其视为未验证正确内容的更技术方法。测试交互是一把双刃剑:测试太多,您就会失去大局——整体功能;测试太少,则会错过对象之间的重要交互。


我相信我会掌握它的,在编写了相当数量的测试之后,我将能够决定要测试什么,不需要测试什么。哦,是的,我有那本书,刚刚读完...很棒的阅读体验,我会推荐给其他人。但我实在记不起来关于要测试什么以及测试多少的建议了。 - Charlino
刚刚再次检查了一下(书)- 看起来我的记忆有点混乱。在其他地方也看到过这个。抱歉。 :) - Arnis Lapsa

2

百分之百的覆盖率是非常理想的,如果你需要大规模重构代码,测试将会确保你的代码规范正确无误,这对你非常有帮助。

个人而言,我并不是百分之百的TDD(有时候太懒了),但是如果你打算使用百分之百的TDD,也许你应该编写一些测试助手来减轻这些重复测试的负担。例如,编写一个助手来测试所有的CRUD操作,使用标准的POST结构和回调函数允许你传入一些评估,这可能会节省你很多时间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接