我正在尝试找出如何正确高效地对我的Asp.net MVC项目进行单元测试。当我开始这个项目时,我买了《Pro ASP.Net MVC》这本书,通过这本书,我学习了有关TDD和单元测试的知识。在看完这些示例之后,加上我现在公司中作为软件工程师在QA方面的工作,我惊讶地发现TDD是多么的棒。所以我开始为我的数据库层、业务层和控制器编写单元测试。在实现之前,所有内容都要经过单元测试。起初,我认为这很棒,但随后情况开始走下坡路。
以下是我开始遇到的问题:
- 我最终不得不编写应用程序代码,以使进行单元测试成为可能。我的意思不是代码有问题,必须修复才能通过单元测试。我的意思是,由于使用了Linq进行数据检索(使用泛型存储库模式),将数据库抽象化为模拟数据库是不可能的。
原因是,在Linq->sql或Linq->entities中,只需执行以下操作即可完成连接:
对控制器编写单元测试很容易,只要返回视图。但是,我的应用程序的主要部分(也是最需要进行单元测试的部分)是一个复杂的 Ajax Web 应用程序。出于各种原因,我决定将应用程序从返回视图更改为返回所需数据的 JSON。 这之后,编写我的单元测试变得非常艰难,因为我没有找到任何好的方法来为非平凡的 JSON 编写单元测试。在苦苦挣扎和浪费大量时间尝试寻找好的方法来测试 JSON 后,我放弃了并删除了所有控制器单元测试(所有控制器操作都是针对该部分应用程序的)。
因此,最后我只能测试 Service 层(BLL)。目前我正在使用 EF4,但是我在使用 linq - > sql 时也遇到了这个问题。我选择了 EF4 模型先方法,因为对我来说,以这种方式定义业务对象并让框架想办法将其转换成 sql 后端是有意义的。开头还好,但由于关系变得繁琐起来。例如,假设我有 Project、User 和 Object 实体。一个 Object 必须与项目相关联,而项目必须与用户相关联。这不仅是数据库特定的规则,也是我的业务规则。但是,假设我想进行保存对象的单元测试(作为一个简单的示例),现在我必须执行以下代码才能确保保存成功:
以下是我开始遇到的问题:
- 我最终不得不编写应用程序代码,以使进行单元测试成为可能。我的意思不是代码有问题,必须修复才能通过单元测试。我的意思是,由于使用了Linq进行数据检索(使用泛型存储库模式),将数据库抽象化为模拟数据库是不可能的。
原因是,在Linq->sql或Linq->entities中,只需执行以下操作即可完成连接:
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 后,我放弃了并删除了所有控制器单元测试(所有控制器操作都是针对该部分应用程序的)。
因此,最后我只能测试 Service 层(BLL)。目前我正在使用 EF4,但是我在使用 linq - > sql 时也遇到了这个问题。我选择了 EF4 模型先方法,因为对我来说,以这种方式定义业务对象并让框架想办法将其转换成 sql 后端是有意义的。开头还好,但由于关系变得繁琐起来。例如,假设我有 Project、User 和 Object 实体。一个 Object 必须与项目相关联,而项目必须与用户相关联。这不仅是数据库特定的规则,也是我的业务规则。但是,假设我想进行保存对象的单元测试(作为一个简单的示例),现在我必须执行以下代码才能确保保存成功:
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个字符名称的项目,则我必须检查每个Object和Project单元测试,以确保新要求不会使任何不相关的单元测试失败。
- 如果
UserService.SaveUser()
方法出现问题,它将导致所有项目和对象单元测试失败,并且除非进行异常排查,否则不会立即注意到原因。
因此,我已经从我的项目中删除了所有服务层单元测试。
我可以继续论述,但到目前为止,我还没有看到任何单元测试实际上可以帮助我而不会妨碍我的方式。我可以看到特定情况下我可以并可能会实现单元测试,例如确保我的数据验证方法正确工作,但这些情况很少见。我的一些问题可能可以缓解,但这不是没有添加额外层来进行单元测试就不能做到的,并因此使我的应用程序存在更多的失败点。因此,在我的代码中已经没有单元测试了。幸运的是,我大量使用源代码控制,因此如果需要,我可以将它们恢复,但我只是看不到它们的意义。
在互联网上到处都是人们谈论TDD单元测试的好处,我不仅指那些狂热的人。很少有人对TDD/单元测试提出了无效的反驳,声称手动通过IDE调试更有效率,或者他们的编码技能非常棒,不需要单元测试。我认识到这两个观点都是完全错误的,尤其是对于需要由多个开发人员维护的项目,但是任何有效的反驳TDD似乎都很少见。
因此,本帖的重点是问,我是否没有理解如何使用TDD和自动单元测试?