如何理解TDD单元测试(Asp.Net MVC项目)?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (15)

我想弄清楚如何正确有效地测试我的Asp.net MVC项目。当我开始这个项目时,我购买了Pro ASP.Net MVC,并在那本书中学习了TDD和单元测试。在看过这些例子之后,以及我在当前公司担任QA软件工程师的事实后,我对TDD看起来真棒有多惊讶。于是我开始研究我的项目,并为我的数据库层,业务层和控制器编写单元测试。在实施之前,所有事情都有单元测试。起初我认为这很棒,但是事情开始走下坡路。

以下是我开始遇到的问题:

  • 我最终编写了应用程序代码,以便可以执行单元测试。我的意思不是这样,因为在我的代码被破坏了,我不得不解决它,所以单元测试通过。我的意思是,将数据库抽象为模拟数据库是不可能的,因为使用linq进行数据检索(使用通用存储库模式)。 原因在于,通过执行linq-> sql或linq->实体可以完成连接: var objs = select p from _container.Projects select p.Objects; 但是,如果你嘲笑数据库层,为了让linq通过单元测试,你必须将linq改为 var objs = select p from _container.Projects join o in _container.Objects on o.ProjectId equals p.Id select o; 这不仅意味着你正在改变你的应用程序逻辑,所以你可以对它进行单元测试,但是你为了测试的唯一目的而让代码效率低下,并且首先使用ORM摆脱了许多优点。 此外,由于我的模型的许多ID都是数据库生成的,因此我证明必须编写额外的代码来处理非数据库测试,因为从不生成ID,并且我仍然需要处理这些情况以便单元测试通过,但它们绝不会出现在真实场景中。 因此我最终抛出了数据库单元测试。
  • 只要我返回视图,为控制器编写单元测试很容易。然而,我的应用程序的主要部分(以及从单元测试中受益最大的部分)是一个复杂的ajax Web应用程序。出于各种原因,我决定将应用程序从返回视图更改为使用我需要的数据返回JSON。发生这种情况后,我的单元测试变得非常痛苦,因为我还没有找到任何为非平凡的json编写单元测试的好方法。 在冲击我的脑后,浪费了大量时间试图找到一种单元测试JSON的好方法后,我放弃并删除了所有控制器单元测试(所有控制器操作都集中在目前为止的这部分应用程序中)。
  • 所以最后我剩下测试服务层(BLL)。现在我正在使用EF4,但是我也有linq-> sql这个问题。我选择了EF4模型优先方法,因为对我来说,这样做是有道理的(定义我的业务对象并让框架找出如何将其转换为SQL后端)。这在开始时很好,但现在由于关系而变得很麻烦。 例如说我有ProjectUserObject实体。一个对象必须与一个项目相关联,并且项目必须与用户相关联。这不仅是数据库特定的规则,这些也是我的业务规则。但是,假设我想做一个单元测试,我可以保存一个对象(一个简单的例子)。我现在必须执行以下代码才能确保保存起作用: User usr = new User { Name = "Me" }; _userService.SaveUser(usr); Project prj = new Project { Name = "Test Project", Owner = usr }; _projectService.SaveProject(prj); Object obj = new Object { Name = "Test Object" }; _objectService.SaveObject(obj); // Perform verifications 为了执行一个单元测试,必须执行所有这些操作有许多问题。这有几个问题。
    • 对于初学者来说,如果我添加一个新的依赖项,比如所有项目都必须属于一个类别,那么我必须进入每个引用项目的单元测试,添加代码以保存类别,然后添加代码以将类别添加到项目中。这对于一个非常简单的业务逻辑改变来说可能是一个巨大的努力,而且几乎没有任何我将要修改的单元测试实际上是为了测试该特性/需求。
    • 如果我然后将验证添加到我的SaveProject方法中,以便项目不能保存,除非它们的名称至少包含5个字符,则必须经过每个对象和项目单元测试以确保新要求不会使任何无关的单元测试都会失败。
    • 如果方法中存在问题,UserService.SaveUser()它将导致所有项目和对象单元测试失败,并且它的原因不会立即引起注意,而不必深入查看异常。

因此我已经从我的项目中删除了所有服务层单元测试。

我可以继续下去,但到目前为止,我还没有看到任何单元测试的方法能够真正帮助我,而不是妨碍我。我可以看到特定的情况,我可以并可能会执行单元测试,例如确保我的数据验证方法正常工作,但这些情况很少。我的一些问题可能可以缓解,但不是没有向我的应用程序添加额外的图层,从而导致更多的失败点,因此我可以进行单元测试。

因此我的代码中没有单元测试。幸运的是我大量使用源代码控制,所以如果需要的话我可以让他们回来,但我只是看不到这一点。

在互联网上的任何地方,我都看到有人在谈论TDD单元测试有多棒,而我不只是在谈论那些狂热的人。少数拒绝TDD /单元测试的人给出了一个不合理的观点,声称他们通过IDE手动调试效率更高,或者他们的编程技能非常棒,他们不需要它。我认识到,这两个论点完全是牛蒡,尤其是对于需要由多个开发人员维护的项目,但对TDD的任何有效反驳似乎都很少见。

所以这篇文章的要点是问,我只是不理解如何使用TDD和自动单元测试?

提问于
用户回答回答于

你可以看看我写的一个示例ASP.NET MVC 2.0项目结构。它提出了一些可能让你开始对单元测试你的控制器逻辑和数据库的概念。就数据库测试而言,它不再是单元测试,而是集成测试。正如你在我的示例中看到的,我使用的是NHibernate,它允许我轻松地切换到为每个测试夹具重新创建的示例SQLite数据库。

最后,在ASP.NET MVC中进行单元测试可能是一种痛苦,如果没有正确分离关注点和抽象,并且使用模拟框架和框架(如MVCContrib.TestHelper)可以使生活更轻松。

以下是控制器单元测试的外观预览

作为对评论的回应,我认为编程是一项具体任务,如何单元测试我复杂的业务应用程序很难给出最终答案。为了能够测试复杂的应用程序之间的耦合应尽可能弱,这可以通过接口和抽象类来实现。我同意,在复杂的应用程序中实现如此弱的耦合并不是一件微不足道的任务。

我可以给你一个建议:如果整个TDD概念很难理解,并且你没有看到它的好处,那么这是可以的。没有人能证明TDD在所有情况下都是有益的。试着设计你的应用程序,让每个班级都有一个责任。如果你发现自己在同一个类中对输入,SQL数据访问和异常处理进行验证,那么你做错了。一旦你实现了这种分离,你将会看到单元测试变得更加容易,甚至当单元测试将推动你的开发时,你甚至可以进入一个阶段:-)

就单元测试而言JsonResultMvcContrib.TestHelper这是一个具体的问题,我给出了具体的答案:

public class MyModel
{
    public string MyProperty { get; set; }
}

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return Json(new MyModel { MyProperty = "value" });
    }
}

测试:

[TestMethod]
public void HomeController_Index_Action_Should_Return_Json_Representation_Of_MyModel()
{
    // arrange
    var sut = new HomeController();

    // act
    var actual = sut.Index();

    // assert
    actual
        .AssertResultIs<JsonResult>()
        .Data
        .ShouldBe<MyModel>("")
        .MyProperty
        .ShouldBe("value");
}
用户回答回答于

听起来问题不在于单元测试,而在于你的工具/平台。我来自Java背景,因此我不必为了满足单元测试而重新编写查询。

测试Json是一件很痛苦的事,你知道是什么。如果你不想测试它,不要;)测试不是100%的覆盖率。它正在测试真正需要测试的东西。你更有可能在复杂的if表达式中获得一个bug,而不是将其添加到地图中,然后将其转换为json。

如果你测试地图的创建是否正确......那么这个json也是很好的机会。当然,这并不总是,但你会知道,一旦你运行它,它就可以工作。或不。这真的很简单。

不过,我可以对第三个问题提供一个真正的评论。你真的不想创建大量的对象图。但有一些方法可以做到这一点。

创建一个名为ObjectMother的单例。有像ObjectMother.createProject()方法。在里面,你创建了虚拟项目实例。这样,12个测试可以使用这种方法,然后你只需要在一个地方改变它。请记住,测试代码需要重构!

此外,你可能会看到类似dbUnit的东西。那么,这就是它在Java世界中所称的。这个想法是,在每次测试运行之前,它都会将数据库置于已知状态。它在测试的设置/拆卸过程中会自动执行此操作。这意味着测试代码可以立即开始测试。实际的虚拟测试数据位于脚本或xml文件中。

扫码关注云+社区