如何用功能测试代码覆盖率驱动出100%功能测试代码覆盖率覆盖率的代码

各位都是怎么进行单元测试的? - 知乎2613被浏览88308分享邀请回答/Test-Driven-Development-Kent-Beck/dp/》过程如下:测试驱动开发的主要过程是:先写功能的测试实现功能代码提交代码(commit -& 保证功能正常)重构功能代码Martin Fowler的《重构》则是这其中的第四步。其优点是:保证你的功能代码都是满足你需求而产出的代码。正好前几天我写了《》。下文以为例,简单地介绍一下如何用测试驱动开发(TDD, Test-Driven Development)的方法来驱动出这个函数库。DDM简介DDM是一个简洁的前端领域模型库,如我在《》一文中所说,它是我对于DDD在前端领域中使用的一个探索。简单地来说,这个库就是对一个数据模型的操作——增、删 、改,然后生成另外一个数据模型。如以Blog模型,删除Author,我们就可以得到一个新的模型。而实现上是因为我们需要RSS模型,我们才需要对原有的模型进行修改。预先式设计如果你对TDD有点了解的话,那么你可能会预先式设计有点疑问。等等,什么是测试驱动开发?测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。流程大概就是这样的,先写个测试 -& 然后运行测试,测试失败 -& 让测试通过 -& 重构。换句简单的话来说,就是 红 -& 绿 -& 重构在DDM项目里,就是一个比较适合TDD的场景。我们知道我们所有的功能,我们也知道我们需要对什么内容进行测试,并且它很简单。因为这个场景下,我们已经知道了我们所需要的功能,所以我们就可以直接设计主要的函数:export class DDM {
constructor() {}
from() {};
get(array) {};
handle() {};
remove(field) {};
上面的就是我们的需要函数,不过在后来因为需要就添加了replace和replaceWithHandle方法。然后,我们就可以编写我们的第一个测试了。第一个驱动开发的测试我们的第一个测试,比较简单,但是也比较麻烦——我们需要构建出基本的轮廓。我们的第一个测试就是要测试我们可以从原来的对象中取出title的值:let ddm = new DDM();
var originObject = {
title: 'hello',
blog: 'fdsf asdf fadsf ',
author: 'phodal'
var newObject = {};
.get(['title'])
.from(originObject)
.to(newObject);
expect(newObject.title).toBe("hello");
对应的,为了实现这个需要基本的功能,我们就可以写一个简单的return来通过测试。
from(originObject) {
return this;
get(array) {
return this;
to(newObject) {
newObject.title = 'hello';
return this;
但是这个功能在我们写下一个测试的时候,它就会出错。ddm
.get(['title', 'phodal'])
.from(originObject)
.to(newObject);
expect(newObject.title).toBe("hello");
expect(newObject.author).toBe("phodal");
但是这也是我们实现功能要做的一步,下一步我们就可以实现真正的功能:在from函数里,复制originObject在get函数里,获取新的对象所需要的key最后,在to函数里,进行复制处理
from(originObject) {
this.originObject = originObject;
return this;
get(array) {
this.newObjectKey = array;
return this;
to(newObject) {
for (var key of this.newObjectKey) {
newObject[key] = this.originObject[key];
return this;
现在,我们已经完成了基本的功能。一个意外的情况在我实现的过程中,我发现如果我传给get函数的array如果是空的话,那么就不work了。于是,就针对这个情况写了个测试,然后实现了这个功能:
get(keyArray) {
if(keyArray) {
this.newObjectKey = keyArray;
this.newObjectKey = [];
return this;
to(newObject) {
if(this.newObjectKey.length > 0){
for (var key of this.newObjectKey) {
newObject[key] = this.originObject[key];
// Clone each property.
for (var prop in this.originObject) {
newObject[prop] = clone(this.originObject[prop]);
return this;
在这个过程中,我还找到了一个clone函数,来替换from中的"="。
from(originObject) {
this.originObject = clone(originObject);
return this;
第三个驱动开发的测试因为有了第一个测试的基础,我们要写下一测试变得非常简单:javascript
dlm.get(['title'])
.from(originObject)
.add('tag', 'hello,world,linux')
.to(newObject);
expect(newObject.tag).toBe("hello,world,linux");
expect(newObject.title).toBe("hello");
expect(newObject.author).toBe(undefined);
在实现的过程中,我又投机取巧了,我创建了一个对象来存储新的对象的key和value:
add(field, value) {
this.objectForAddRemove[field] = value;
return this;
同样的,在to方法里,对其进行处理:
to(newObject) {
function cloneObjectForAddRemove() {
for (var prop in this.objectForAddRemove) {
newObject[prop] = this.objectForAddRemove[prop];
function cloneToNewObjectByKey() {
for (var key of this.newObjectKey) {
newObject[key] = this.originObject[key];
function deepCloneObject() {
// Clone each property.
for (var prop in this.originObject) {
newObject[prop] = clone(this.originObject[prop]);
cloneObjectForAddRemove.call(this);
if (this.newObjectKey.length > 0) {
cloneToNewObjectByKey.call(this);
deepCloneObject.call(this);
return this;
在这个函数里,我们用cloneObjectForAddRemove函数来复制将要添加的key和value到新的对象里。remove和handle函数对于剩下的remove和handle来说,他们实现起来都是类似的:存储相应的对象操作然后在to函数里进行处理编写测试:
function handler(blog) {
return blog[0];
ddm.get(['title', 'blog', 'author'])
.from(originObject)
.handle("blog", handler)
.to(newObject);
expect(newObject.blog).toBe('A');
然后实现功能:
remove(field) {
this.objectKeyForRemove.push(field);
return this;
handle(field, handle) {
this.handleFunction.push({
field: field,
handle: handle
return this;
这一切看上去都很自然,然后我们就可以对其进行重构了。100%的测试覆盖率由于,我们先编写了测试,再实现代码,所以我们编写的代码都有对应的测试。因此,我们可以轻松实现相当高的测试覆盖率。在这个Case下,由于业务场景比较简单,要实现100%的测试覆盖率就是一件很简单的事。(PS: 我不是TDD的死忠,只是有时候它真的很美。)各们看官可以看相关的提交历史,GitHub: 293 条评论分享收藏感谢收起测试驱动开发的艺术(科斯科拉)【电子书籍下载 epub txt pdf doc 】
书籍作者:
书籍出版:
人民邮电出版社
书籍页数:
书籍ISBN:
书籍人气:
推荐指数:
测试驱动开发的艺术《测试驱动开发的艺术》介绍了一种更快更好的软件开发方法——测试驱动开发。全书共分三部分:第一部分讲述了TDD和ATDD的相关知识、基本概念、方法,为测试驱动开发打下基础;第二部分将测试驱动开发用于具体的实践,重点讲解了TDD的各种技术;第三部分着重介绍了验收测试驱动开发,包括Fit框架、实现验收测试的方法等,最后讲解了引入TDD的各种技巧。  《测试驱动开发的艺术》浓缩了作者多年的开发经验,适合各类Java开发人员学习参考。第一部分 TDD入门第1章 综述1.1 挑战:用正确的方法解决正确的问题1.1.1 糟糕的代码质量1.1.2 不能满足客户需求1.2 解决方案:测试驱动1.2.1 高质量的TDD1.2.2 用ATDD满足客户需求1.2.3 这对我有什么好处1.3 正确地做事:TDD1.3.1 测试-编码-重构1.3.2 增量式开发1.3.3 重构以保持代码的健康1.3.4 保证软件正常运行1.4 做正确的事:ATDD1.4.1 名字的含义1.4.2 紧密协作1.4.3 把测试作为沟通的共同语言1.5 TDD工具1.5.1 使用xUnit做单元测试1.5.2 支持ATDD的测试框架1.5.3 持续集成及构建1.5.4 代码覆盖率1.6 小结第2章 TDD入门第3章 小步重构第4章 TDD的概念与模式第二部分 针对特定技术应用TDD第5章 测试驱动Web组件第6章 测试驱动数据访问第7章 测试驱动不可预测功能第8章 测试驱动Swing代码第三部分 基于ATDD构建产品第9章 解析验收测试驱动开发第10章 用Fit创建验收测试第11章 执行验收测试的策略第12章 TDD应用附录A JUnit 4简明教程附录B JUnit 3.8简明教程附录C EasyMock简明教程附录D 通过Ant运行测试相关资源安全检查中...
请打开浏览器的javascript,然后刷新浏览器
< 浏览器安全检查中...
还剩 5 秒&上回说到,小艾明白了单元测试的重要性之后,也明白了单元测试需要测什么, 这让他的开发效率明显有了提高,而bug密度有了明显下降。但是,小艾的代码依然保持着高耦合的风格,因此编写单元测试的时候遇到了不少的阻碍,导致了测试覆盖率偏低,但是他却发现组长的测试覆盖率居然能逼近100%。
最让小艾讶异的是,产品负责人带来的用户反馈要求将已有代码进行较大幅度的变更,这个情况他简直没办法相信。就在这时,小艾听到组长说不用担心,TDD能够应付这种情况。TDD? 小艾对这个隐约有印象,似乎曾经听组长提起过,但自己对此一无所知,于是,他又来到了组长面前。
组长看到小艾对此十分感兴趣,于是也细心地讲了起来……
什么是测试驱动开发,测试驱动的工作流程
测试驱动开发每次针对一个很小的功能点,通常是小到一个单独的方法。流程:
&&&&在实现新功能之前,先考虑代码的使用需求(包括功能、过程、接口等),为其编写测试代码。
&&&&让新写的测试代码和已有的测试代码一起运行。
&&&&为新功能编写最少的实现代码,切记,是最少的实现代码。
&&&&再次让新测试代码和已有代码一起运行,根据运行结果调整实现代码,直到全部测试代码通过。
&&&&在此过程中,积极地对代码进行重构,优化代码。
&&&&重复上述操作,直到完成全部功能的开发。
测试驱动开发是一种编写软件的模式,是一种敏捷开发实践。其指导思想就是让开发人员在编写功能代码之前,根据需求编写测试代码。思考如何对将要实现的功能进行验证,并完成单元测试脚本的编写,然后编写足够,仅仅是足够的功能代码满足这些测试用例,直至测试通过。
递增地在迭代中增加新功能的单元测试和功能代码编写,直到完成全部功能的开发。
测试驱动开发已经不再是单纯的测试行为,而是上升到了一种设计行为,或者说,测试驱动开发的目的不是为了验证代码实现,而是为了描述一段代码的用途和用法的设计规格说明。且这种描述是无二义的,是可执行验证的。
测试驱动开发与先开发后测试的异同
&&&&都对底层功能进行验证
&&&&都得到了单元测试资产
&&&&使项目容忍变化,可通过单元测试来保证引入的变化不会带来负面影响。
&&&&测试覆盖率不同:测试驱动开发要求考虑全部可能的测试,几乎可达100%覆盖率。后者则相对较低。
&&&&代码可测性不同:测试驱动开发中的代码天生具有可测性,后者则不能保证。
&&&&对需求的明晰程度不同:测试驱动开发编写测试代码的过程就是从代码级别对需求逐渐明晰的过程。
测试驱动开发好处多
从上述的异同点中就可以看到,测试驱动开发所倡导的可测试的代码、单元测试覆盖、重构和更优化的设计之间,是互为因果、良性循环的行为。测试优先使得代码天生具有可测性,因此保证了近乎100%的测试覆盖率,因而bug密度较低,有利于更早发现bug。
而最大的好处在于它对代码的重构,重构可以消除重复设计,优化设计结构,使接口更简单,产生高内聚,低耦合的代码,代码复杂度的降低,也让其更便于维护。这也意味着能够最终从设计层面对代码做出改进。
测试驱动开发是敏捷开发的基础,而测试优先的方式,也能够让代码更好地适应多变的需求。
听完组长一席话,小艾明白了测试驱动开发的好处,但是很好奇,既然这么好用为什么好像没有被广泛使用呢?因为测试驱动开发相对传统开发来说,是一种观念的改变,需要时间让大家慢慢来接受。
小艾回想起在开发组的这段时间,从第一次做不正规的单元测试,到后面的测试驱动开发,既增长了自己的见识,又长了自己的本事。
第四章的内容到这里也要告一段落了,小艾协助开发人员的日子也走到了末期,应该回到自己的测试岗位去了,对代码有一定了解的小艾,相信在接下来的测试工作中,会更加得心应手~
想要第一时间看到这一系列文章的更新及更多精彩内容可以扫描下面二维码关注微信公众号: 倚楼听风雨的如月
阅读(...) 评论()使用IdleTest进行TDD单元测试驱动开发演练(1)
来源:博客园
【前言】&#13;开发工具:Visual Studio 2012&#13;测试库:Visual Studio 2012自带的MSTest&#13;DI框架:Unity &#13;数据持久层:Entity Framework&#13;前端UI:ASP.NET MVC 4.0&#13;需求:我这里假设只满足两个功能,一个用户注册,另一个则是登陆的功能,借助于一些DDD思想,我将从领域层(或者常说的BLL)开始开发,当然每一层都是采用TDD,按我喜欢的做法就是“接口先行,测试驱动”,不废话,直奔主题吧。&#13;有关VS2012的单元测试请参见《》&#13;有关测试中使用的IdleTest库请参见&#13; &#13;一、首先来创建解决方案与项目的结构。&#13; &#13;1. 创建空白解决方案“IdleTest.TDDEntityFramework”,新建解决方案文件夹“Interfaces”,并在文件夹内创建两个项目 “IdleTest.TDDEntityFramework.IRepositories” 和 “IdleTest.TDDEntityFramework.IServices”。&#13;2. 直接在解决方案下创建类库项目 “IdleTest.TDDEntityFramework.Services”、“IdleTest.TDDEntityFramework.Models” 和 “IdleTest.TDDEntityFramework.Repositories”&#13;3. 在解决方案下创建MVC4项目"IdleTest.TDDEntityFramework.MvcUI"作为最终的UI,我这里选择空模板,解决方案初始结构初始结构图如下&#13;&#13;4. 把所有类库项目中自动生成的“Class1.cs”文件删除。&#13;5. 使用Visio画出解决方案中各项目的关系(如下图),这图画的是项目关系,实际上这些项目内的类也都遵循这样的关系。例如本项目只有一个Model,即UserModel,那么“IdleTest.TDDEntityFramework.IRepositories”下就相应将类命名为“IUserRepository”,“IdleTest.TDDEntityFramework.IServices”对应“IUserService”,以此类推,非接口则去掉前缀“I”。这是我个人的一些习惯,每个人可能命名方式可能不太一样,这很正常,但是如果是超过一个人来共同开发,则应将规范统一,俗话说“约定优于配置”嘛。&#13;&#13;6. 这里只是自己演练TDD的Demo而已,将不使用“UnitOfWork”,其他也可能会缺少不少功能,因为不低不在于Entity Framework或MVC等等,而关注的只是单元测试驱动开发罢了。&#13; &#13;二、测试前的编码以及其他方面的准备&#13; &#13;7. 在“IdleTest.TDDEntityFramework.Models”下添加类“UserModel”。&#13;&#13;
public class UserModel&#13;
public string LoginName { get; set; }&#13;&#13;
public string Password { get; set; }&#13;&#13;
public int Age { get; set; }&#13;
}&#13;&#13;UserModel&#13;8. 分别在项目“IdleTest.TDDEntityFramework.IRepositories”和“IdleTest.TDDEntityFramework.IServices”下添加引用“IdleTest.TDDEntityFramework.Models”,并分别添加接口“IUserRepository”、“IRepository”和“IUserService”。 &#13;&#13;
public interface IUserRepository : IRepository&UserModel, string&&#13;
}&#13;&#13;&#13;
public interface IRepository&TEntity, TKey& where TEntity : class&#13;
IEnumerable&TEntity& Get(&#13;
Expression&Func&TEntity, bool&& filter = null,&#13;
Func&IQueryable&TEntity&, IOrderedQueryable&TEntity&& orderBy = null,&#13;
string includeProperties = "");&#13;&#13;
TEntity GetSingle(TKey id);&#13;&#13;
void Insert(TEntity entity);&#13;&#13;
void Update(TEntity entityToUpdate);&#13;&#13;
void Delete(TKey id);&#13;&#13;
void Delete(TEntity entityToDelete);&#13;
}&#13;&#13;IRepository&#13;&#13;
public interface IUserService&#13;
bool Login(UserModel model);&#13;&#13;
bool Register(UserModel model);&#13;&#13;
UserModel GetModel(string loginName);&#13;
}&#13;&#13;IUserService&#13;  那么借助DDD的一些思想,这里的IUserService体现着功能需求,Service这层的代码完全由业务需求确定,因而IUserService只编写了三个方法。而Repository这层则不去关心业务,只是常规性的公开且提供一些方法出来,这在很多项目中几乎都是确定,孤儿IRepository也就自然而然具有了增删改查的功能了。&#13;9. 开始涉及单元测试,创建解决方案文件夹“Tests”,并在该文件夹下创建单元测试项目“IdleTest.TDDEntityFramework.ServiceTest”,添加引 用“IdleTest.TDDEntityFramework.IRepositories”、“IdleTest.TDDEntityFramework.IServices”、“IdleTest.TDDEntityFramework.Services”、“IdleTest.TDDEntityFramework.Models”,紧接着对“IdleTest.TDDEntityFramework.IRepositories”添加“Fakes程序集”(有关Fakes可参照《》)。&#13;10. 在解决方案物理路径下创建文件夹“libs”,并将“IdleTest”中相关dll拷贝进去。接着在项目“IdleTest.TDDEntityFramework.ServiceTest”添加引用,在“引用管理器”中单击“浏览”按钮,找到刚刚创建的“libs”文件夹,并添加下图所示引用。有关IdleTest可参照从下载编译。&#13; &#13; &#13;三、编写单元测试,边测试边修改代码&#13; &#13;11. 我将在刚添加的测试项目中编写一个针对“IUserService”的测试基类“BaseUserServiceTest”(关于对接口的测试可以参照《》)。&#13;&#13;using IdleT&#13;using IdleTest.MST&#13;using IdleTest.TDDEntityFramework.IS&#13;using IdleTest.TDDEntityFramework.IRepositories.F&#13;using S&#13;using System.Collections.G&#13;using System.L&#13;using System.Linq.E&#13;using System.T&#13;using System.Threading.T&#13;using IdleTest.TDDEntityFramework.M&#13;using IdleTest.TDDEntityFramework.IR&#13;using Microsoft.VisualStudio.TestTools.UnitT&#13;&#13;namespace IdleTest.TDDEntityFramework.ServiceTest&#13;{&#13;
public abstract class BaseUserServiceTest&#13;
protected string ExistedLoginName = "zhangsan";&#13;&#13;
protected string ExistedPassword = "123456";&#13;&#13;
protected string NotExistedLoginName = "zhangsan1";&#13;&#13;
protected string NotExistedPassword = "123";&#13;&#13;
private IUserRepository userR&#13;&#13;
protected IList&UserModel& ExistedU&#13;&#13;
protected abstract IUserService UserService&#13;
/// &summary&&#13;
/// IUserRepository模拟对象&#13;
/// &/summary&&#13;
public virtual IUserRepository UserRepository&#13;
if (this.userRepository == null)&#13;
StubIUserRepository stubUserRepository = new StubIUserRepository();&#13;
//模拟Get方法&#13;
stubUserRepository.GetExpressionOfFuncOfUserModelBooleanFuncOfIQueryableOfUserModelIOrderedQueryableOfUserModelString&#13;
= (x, y, z) =&&#13;
return this.ExistedUsers.Where&UserModel&(x.Compile());&#13;
};&#13;&#13;
//模拟GetSingle方法&#13;
stubUserRepository.GetSingleString = p =& this.ExistedUsers.FirstOrDefault&UserModel&(o =& o.LoginName == p);&#13;&#13;
//模拟Insert方法&#13;
stubUserRepository.InsertUserModel = (p) =& this.ExistedUsers.Add(p);&#13;&#13;
this.userRepository = stubUserR&#13;
}&#13;&#13;
return this.userR&#13;
}&#13;&#13;
[TestInitialize]&#13;
public void InitUserList()&#13;
//每次测试前都初始化&#13;
this.ExistedUsers = new List&UserModel& { new UserModel { LoginName = ExistedLoginName, Password = ExistedPassword } };&#13;
}&#13;&#13;
public virtual void LoginTest()&#13;
//验证登陆失败的场景&#13;
AssertCommon.AssertBoolean&UserModel&(&#13;
new UserModel[] { &#13;
null, new UserModel(),&#13;
new UserModel { LoginName = string.Empty, Password = ExistedPassword }, //账户为空&#13;
new UserModel { LoginName = ExistedLoginName, Password = string.Empty }, //密码为空&#13;
new UserModel { LoginName = ExistedLoginName, Password = NotExistedPassword }, //密码错误&#13;
new UserModel { LoginName = NotExistedLoginName, Password = NotExistedPassword },
//账户密码错误
new UserModel { LoginName = NotExistedLoginName, Password = ExistedLoginName }
//账户错误&#13;
}, false, p =& UserService.Login(p));&#13;&#13;
//账户密码正确,验证成功,这里假设正确的账户密码是"zhangsan"、"123456"&#13;
UserModel model = new UserModel { LoginName = ExistedLoginName, Password = ExistedPassword };&#13;
AssertCommon.AssertEqual&bool&(true, UserService.Login(model));&#13;
}&#13;&#13;
public virtual void RegisterTest()&#13;
//验证注册失败的场景&#13;
AssertCommon.AssertBoolean&UserModel&(&#13;
new UserModel[] { &#13;
null, new UserModel(),&#13;
new UserModel { LoginName = string.Empty, Password = NotExistedPassword }, //账户为空&#13;
new UserModel { LoginName = NotExistedLoginName, Password = string.Empty }, //密码为空&#13;
new UserModel { LoginName = ExistedLoginName, Password = NotExistedPassword }, //账户已存在&#13;
}, false, p =& UserService.Register(p));&#13;&#13;
//验证注册成功的场景&#13;
//密码与他人相同也可注册&#13;
UserModel register1 = new UserModel { LoginName = "register1", Password = ExistedPassword };&#13;
UserModel register2 = new UserModel { LoginName = "register2", Password = NotExistedPassword };&#13;
UserModel register3 = new UserModel { LoginName = "register3", Password = NotExistedPassword, Age = 18 }; &#13;
AssertCommon.AssertBoolean&UserModel&(&#13;
new UserModel[] { register1, register2, register3 }, true, p =& UserService.Register(p));&#13;&#13;
//获取用户且应与注册的信息保持一致&#13;
UserModel actualRegister1 = UserService.GetModel(register1.LoginName);&#13;
AssertCommon.AssertEqual&string&(register1.LoginName, actualRegister1.LoginName);&#13;
AssertCommon.AssertEqual&string&(register1.Password, actualRegister1.Password);&#13;
AssertCommon.AssertEqual&int&(register1.Age, actualRegister1.Age);&#13;&#13;
UserModel actualRegister2 = UserService.GetModel(register2.LoginName);&#13;
AssertCommon.AssertEqual&string&(register2.LoginName, actualRegister2.LoginName);&#13;
AssertCommon.AssertEqual&string&(register2.Password, actualRegister2.Password);&#13;
AssertCommon.AssertEqual&int&(register2.Age, actualRegister2.Age);&#13;&#13;
UserModel actualRegister3 = UserService.GetModel(register3.LoginName);&#13;
AssertCommon.AssertEqual&string&(register3.LoginName, actualRegister3.LoginName);&#13;
AssertCommon.AssertEqual&string&(register3.Password, actualRegister3.Password);&#13;
AssertCommon.AssertEqual&int&(register3.Age, actualRegister3.Age);&#13;
}&#13;&#13;
public virtual void GetModelTest()&#13;
AssertCommon.AssertIsNull&string, UserModel&(TestCommon.GetEmptyStrings(), true, p =& UserService.GetModel(p));&#13;
AssertCommon.AssertIsNull(true, UserService.GetModel(NotExistedLoginName));&#13;&#13;
UserModel actual = UserService.GetModel(ExistedLoginName);&#13;
AssertCommon.AssertEqual&string&(ExistedLoginName, actual.LoginName);&#13;
AssertCommon.AssertEqual&string&(ExistedPassword, actual.Password);&#13;
}&#13;}&#13;&#13;BaseUserServiceTest&#13; &#13;  BaseUserServiceTest类本身不会具有任何测试,只有子类去继承它,且实现抽象属性“UserService”、Override相应的测试方法(LoginTest、RegisterTest、GetModelTest)并声明“TestMethod”特性后才能进行测试。&#13;12. 在测试项目再编写类UserServiceTest,继承BaseUserServiceTest。 &#13;&#13;
[TestClass]&#13;
public class UserServiceTest : BaseUserServiceTest&#13;
protected override IUserService UserService&#13;
get { return new UserService(this.UserRepository); }&#13;
}&#13;&#13;
[TestMethod]&#13;
public override void GetModelTest()&#13;
base.GetModelTest();&#13;
}&#13;&#13;
[TestMethod]&#13;
public override void LoginTest()&#13;
base.LoginTest();&#13;
}&#13;&#13;
[TestMethod]&#13;
public override void RegisterTest()&#13;
base.RegisterTest();&#13;
}&#13;&#13;UserServiceTest&#13;  由于父类已做好了相应的测试代码,此时编写UserServiceTest就有点一劳永逸的感觉了。&#13;  注意在实现“UserService”属性时,编写如下图所示代码后按“Alt+Shift+F10”在弹出的小菜单中选中“为UserService生成类”回车,这时发现它生成在了我们的测试项目中,我暂时不会去理会这些,现在最要紧的是我需要在最短时间最少代码量上使得我的测试通过。&#13;&#13;  接着去修改刚生成的UserService类。 &#13;&#13;
public class UserService : IUserService&#13;
private IUserRepository userR&#13;&#13;
public UserService(IUserRepository userRepository)&#13;
// TODO: Complete member initialization&#13;
this.userRepository = userR&#13;
}&#13;&#13;
public bool Login(UserModel model)&#13;
throw new NotImplementedException();&#13;
}&#13;&#13;
public bool Register(UserModel model)&#13;
throw new NotImplementedException();&#13;
}&#13;&#13;
public UserModel GetModel(string loginName)&#13;
throw new NotImplementedException();&#13;
}&#13;&#13;UserService&#13;13. 生成之后打开“测试资源管理器”稍等几秒即可发现三个需要测试的方法呈现了。此时测试当然都是全部不通过。继续往下修改UserService,直至测试通过。&#13; &#13;&#13;
public class UserService : IUserService&#13;
private IUserRepository userR&#13;&#13;
public UserService(IUserRepository userRepository)&#13;
// TODO: Complete member initialization&#13;
this.userRepository = userR&#13;
}&#13;&#13;
#region IUserService成员&#13;
public bool Login(UserModel model)&#13;
if (!IsValidModel(model))&#13;
return false;&#13;
}&#13;&#13;
IList&UserModel& list = &#13;
userRepository.Get(p =& p.LoginName == model.LoginName && p.Password == model.Password).ToList();&#13;&#13;
return list != null && list.Count & 0;&#13;
}&#13;&#13;
public bool Register(UserModel model)&#13;
if (!IsValidModel(model))&#13;
return false;&#13;
}&#13;&#13;
if (GetModel(model.LoginName) != null)&#13;
return false;&#13;
}&#13;&#13;
userRepository.Insert(model);&#13;
return true;&#13;
}&#13;&#13;
public UserModel GetModel(string loginName)&#13;
if (!string.IsNullOrEmpty(loginName))&#13;
return userRepository.GetSingle(loginName);&#13;&#13;
return null;&#13;
#endregion&#13;&#13;
private bool IsValidModel(UserModel model)&#13;
return model != null && !string.IsNullOrEmpty(model.LoginName) && !string.IsNullOrEmpty(model.Password);&#13;
}&#13;&#13;UserService&#13; &#13;14. 此时测试已通过,查看代码覆盖率,双击”UserService“下未达到100%覆盖率的行(如下图所示)可以查看哪些代码尚未覆盖,然后酌情再看是否需要增加或修改代码以使覆盖率达到100%,我这里分析当前未覆盖的对项目没有什么影响,故不再修改。&#13;&#13;15. 最后将UserService类剪切到项目”IdleTest.TDDEntityFramework.Services“,添加引用,修改相应命名空间。&#13;&#13;再次运行测试并顺利通过,那么这一阶段的开发与单元测试均大功告成。&#13; &#13;【总结】&#13;
  上述过程简言之,就是先搭建VS解决方案的项目结构,然后编写Model(此无需测试,也是整个项目传递数据的基本),再写项目需要的接口,接着针对接口编写单元测试,&#13;最后才是编写实现接口的类代码。&#13;   对于实现接口的类中的一些方法(如“UserService”类的“IsValidModel”方法)我并没有针对它编写测试,首先它是一个私有方法(关于私有方法需不需要测试的争论貌似现在还没有统一的结论,鄙人能力有限,不敢妄加评价);其次即使它是一个public方法,我也仍然不会去测试它,因为它只是为“IUserService”接口成员服务的,或者说该方法原本就不需要,只是我写代码中重构出来,编写完UserService我只关心该类中的“IUserService”接口成员,所以……
其实,这里也可以通过代码覆盖率看到,即使没有专门对“IsValidModel”方法编写相应测试,但是它的覆盖率仍然是100%,我不能确定私有方法到底要不要测试,但是在这里我不测“IsValidModel”方法肯定没有错。&#13;   测试基类“BaseUserServiceTest”是针对“IUserService”接口编写的,而它的子类貌似什么都不做,我之所以这么写,只是为了以后如果有新的类实现“IUserService”接口&#13;时,我仍然只需要简单的添加“BaseUserServiceTest”的一个子类,就可以完成测试,文中貌似也提到,有种一劳永逸的感觉,除非接口改变,否则对类的修改等等基本都不会影响&#13;到原有测试。这样就足以保证了以后修改bug、代码重构或需求变化时对代码修改后仍能。&#13; &#13;  由于使用了依赖注入,故而测试时就可以隔离依赖,文中Service层原本是依赖Repository,但是我这里在未具体实现Repository前都不会影响对Service层的开发与测试。&#13;   TDD前期工作量比较大,但是对于后期代码(例如整体测试修改bug、代码重构或需求变化时对代码修改)质量的保证是非常可靠的。&#13;  未完待续。。。。。。
免责声明:本站部分内容、图片、文字、视频等来自于互联网,仅供大家学习与交流。相关内容如涉嫌侵犯您的知识产权或其他合法权益,请向本站发送有效通知,我们会及时处理。反馈邮箱&&&&。
学生服务号
在线咨询,奖学金返现,名师点评,等你来互动

我要回帖

更多关于 代码覆盖率 的文章

 

随机推荐