我只是不了解TDD单元测试(Asp.Net MVC项目)?

Kal*_*exx 16 architecture tdd asp.net-mvc unit-testing

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

以下是我遇到的问题:

  • 我最终编写了应用程序代码,以便能够执行单元测试.我的意思并不是很好,因为我的代码被破坏了,我不得不修复它以便单元测试通过.我的意思是,由于使用linq进行数据检索(使用通用存储库模式),将数据库抽象为模拟数据库是不可能的.

    原因是使用linq-> sql或linq->实体,您只需执行以下操作即可进行连接:

    var objs = select p from _container.Projects select p.Objects;
    
    Run Code Online (Sandbox Code Playgroud)

    但是,如果您模拟数据库层,为了让linq通过单元测试,您必须将linq更改为

    var objs = select p from _container.Projects
           join o in _container.Objects on o.ProjectId equals p.Id
           select o;
    
    Run Code Online (Sandbox Code Playgroud)

    这不仅意味着你正在改变你的应用程序逻辑,所以您可以单元测试,但你让你的代码的可测试性的唯一目的效率较低,并且摆脱了很多使用ORM在首位的优势.

    此外,由于我的模型的许多ID都是数据库生成的,因此我证明必须编写额外的代码来处理非数据库测试,因为ID从未生成过,我不得不处理这些情况以便单元测试通过,但它们永远不会出现在真实场景中.

    因此,我最终抛弃了我的数据库单元测试.

  • 只要我返回视图,编写控制器的单元测试就很容易.但是,我的应用程序的主要部分(以及从单元测试中受益最多的部分)是一个复杂的ajax Web应用程序.出于各种原因,我决定将应用程序从返回视图更改为返回带有我需要的数据的JSON.在这发生之后,我的单元测试变得非常痛苦,因为我没有找到任何好的方法来为非平凡的json编写单元测试.

    经过大量的时间试图找到一个单元测试JSON的好方法后,我放弃并删除了所有的控制器单元测试(到目前为止,所有控制器操作都集中在应用程序的这一部分).

  • 所以我最后还是测试了服务层(BLL).现在我正在使用EF4,但我也遇到了linq-> sql这个问题.我选择使用EF4模型优先方法,因为对我而言,这样做是有道理的(定义我的业务对象并让框架弄清楚如何将其转换为sql后端).这在开始时很好,但现在由于人际关系而变得很麻烦.

    例如说我有Project,UserObject实体.一个对象必须与项目相关联,并且项目必须与用户相关联.这不仅是数据库特定的规则,这些也是我的业务规则.但是,假设我想进行单元测试,我可以保存一个对象(举个简单的例子).我现在必须执行以下代码以确保保存工作:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    为了执行一个单元测试,必须执行所有这些操作有很多问题.这有几个问题.

    • 对于初学者,如果我添加一个新的依赖项,例如所有项目必须属于一个类别,我必须进入每个引用项目的单个单元测试,添加代码以保存类别,然后添加代码以将类别添加到项目中.对于一个非常简单的业务逻辑更改,这可能是一项巨大的努力,但是我将为此要求修改的几乎所有单元测试实际上都不是为了测试该功能/要求.
    • 如果我然后向我的SaveProject方法添加验证,那么除非项目的名称至少包含5个字符,否则无法保存项目,然后我必须通过每个对象和项目单元测试以确保新要求不会任何不相关的单元测试都会失败
    • 如果方法中存在问题,UserService.SaveUser()则会导致所有项目和对象单元测试失败,并且在不必挖掘异常的情况下,原因不会立即引起注意.

因此,我从项目中删除了所有服务层单元测试.

我可以继续,但到目前为止,我还没有看到任何方法进行单元测试,实际上帮助我而不是妨碍我.我可以看到具体情况,我可以,也可能会实施单元测试,例如确保我的数据验证方法正常工作,但这些情况很少见.我的一些问题可能会得到缓解,但不能在我的应用程序中添加额外的层,从而产生更多的失败点,这样我才能进行单元测试.

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

在互联网上的任何地方,我都看到人们在谈论TDD单元测试有多棒,而且我不只是在谈论狂热的人.解雇TDD/Unit测试的少数人提出了不好的论据,声称他们通过IDE手动调试更有效率,或者他们的编码技能非常惊人,他们不需要它.我认识到这两个论点都是彻头彻尾的公牛,特别是对于一个需要由多个开发人员维护的项目,但对TDD的任何有效反驳似乎都很少.

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

Dar*_*rov 6

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

最后在ASP.NET MVC中进行单元测试可能会很麻烦,如果没有正确分离关注点和抽象,并使用模拟框架和框架,如MVCContrib.TestHelper可以让您的生活更轻松.

以下是控制器单元测试结果的预览.


更新:

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

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

至于单元测试a JsonResult,MvcContrib.TestHelper这是一个具体的问题,我给出了具体的答案:

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

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

而且测试:

[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");
}
Run Code Online (Sandbox Code Playgroud)