.NET项目开发—浅谈面向接口编程、可测试性、单元测试、迭代重构(项目小结)

阅读目录:

  • 1.开篇介绍 
  • 2.迭代测试、重构(强制性面向接口编程,要求代码具有可测试性)
    • 2.1.面向接口编程的两个设计误区

      • 2.1.1.接口的依赖倒置
      • 2.1.2.接口对实体的抽象 
    • 2.2.迭代单元测试、重构(代码可测试)
      • 2.2.1.LINQ表达式对单元测试的影响 

1】开篇介绍

最近一段时间结束了一个小项目的开发,觉得有些好东西值得总结与分享,所以花点时间整理成文章;

大多数情况下我们都知道这些概念,面向接口编程是老生常谈的话题了,有几年编程经验的都知道怎么运用;单元测试其实在前几年不怎么被重视,然而最近逐渐的浮现在我们眼前,而且被提起的频率也大了很多了,包括重构、可测试性都慢慢的贴近我们,我们只有亲自动手去使用它才能领悟其精髓;

下面我将总结一下我对上述几个概念之间的新体会;

2】迭代测试、重构(强制性面向接口编程,要求代码具有可测试性)

【面向接口编程简述】

面向接口编程要求我们彼此之间使用接口的方式调用,将一切可能存在变化的实例隔离在内部,这些实例都只是一个可以随时被替换的幕后劳动者;但是面向接口编程是需要一定的设计能力,能否合理的将对象抽象出接口来,真是一句两句话无法概括的;

面向接口设计其实本人觉得会有一些细节的设计误区,既然抽象出接口那么就存在接口依赖的问题,还有就是对于Entity类型的抽象是否合理,是否会打乱Entity的清晰度,因为我们对DomainModel的理解是DomainEntity是一个POCO的对象,就是一个很简单的纯净的类实体,一目了然,如果换成接口对后面的DDD的开发会有很大的麻烦,因为对接口的支持无法做到简单的持久化,还有就是思维上的转变也有很大的麻烦;

2.1】面向接口编程的两个设计误区

首先我觉得第一个误区就是接口的依赖问题,接口的依赖不是一个小问题,在真实的项目中层之间的依赖是有严格的要求的,传统分层架构要求上层只能够依赖下层,而DDD分层架构是DomaiModel层绝对的无任何依赖,DomainModel不会去引用下层的基础设施,因为它要求绝对的干净;但是发现还是有很多的项目没有能够理解DDD的这点优点;然后就是对于层之间的实体抽取接口,其实这点真的有待商量,DataAccess Layer中的数据实体严格意义说是DTO对象是用来过度到Business Layer中使用的,那么如果将DataAccess中的DTO设计成接口类型对外提供使用,Business Layer 就依赖上了DataAccess Layer了,所以还是需要根据项目的具体需求来平衡,下面我们看一下示例及分析;

2.1.1】 接口的依赖倒置

传统的三层架构,在Facade中调用BLL的方法,BLL调用DAL方法,这难道不是违背了“单一职责”原则吗;一直我们都在强调“单一职责”设计原则,为什么很多项目的每层之间都是直接使用下层的接口,特别是我们的核心DomainModel层中,本来就是很干净的纯业务处理,来一个什么数据访问的接口真的很不美;

图1:

这种架构应该是大部分的项目的结构,我们应该一眼就看出问题在哪里了,很明显在Bl Layer中直接使用了Da Layer 相关接口获取数据,单纯从这一点就有点违背单一职责设计原则;

图2:

接口依赖倒置到底是谁向谁倒置了,第一张图是业务层依赖了数据层,详细点就是依赖了数据访问的接口;第二张图中业务层没有依赖任何东西,细心的朋友应该看到第二张图中多了一个“DomainModel Event route ” 的东西,这是一种机制,目的是让领域内部产生领域事件,类似事件路由的效果,基础设施要做任何的事情跟DomaiModel Entity 本身没有任何关系;

2.1.2】 接口对实体的抽象

实体的抽象如果变成接口会很别扭,我们对实体的最直观的认识是一个很POCO的对象,但是如果你在设计的时候将数据访问的DTO都设计成接口是否是有点不必要,有两个情况下可以平衡这种需要,第一如果你的DTO不需要业务层传入数据层那么无所谓的,那么如果是需要业务层传入数据层的接口肯定是不行的,这里就是觉得将实体与接口的概念扯到一起很不直观,像业务实体你把它抽层接口对持久化来说就是一个问题了;

2.2】迭代单元测试、重构(代码可测试)

其实这篇文章的主要内容是在这一节,上一节我说了一下我对接口抽象的一点个人看法;这一节我们将通过一个具体的示例来看一下这篇文章的重要内容,看看单元测试如何与持续迭代重构完美结合的,在编写单元测试用例的时候我们将发现代码被逐渐的重构的很优美,面向接口编程再一次被提到一个高度;

在我们编写代码的时候一般情况下无法验证我们的代码好与坏,光凭嘴说也很难断定每个人的设计思路是否完全正确的,所以代码可测试性将成为验证你所编写的代码的质量的一个重要指标;

单元测试与重构将是一个持续迭代的过程,很多人并不太关心重构和单元测试,其实是因为我们大部分情况下在开发一次性的交付的项目而不是持续更新的产品,所以单元测试、重构被我们所忽视,面向接口编程也被我们时而记起也时而忘记,下面我们来看一下如何编写可测试性的代码;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-24
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace UnittestDemo
 9 {
10     using System.Linq.Expressions;
11     using System;
12
13     public static class ServiceReport
14     {
15         public static Report QueryReport(string queryWhere)
16         {
17             return new Report();
18         }
19     }
20 }

View Code

这是一个很简单的静态类,主要目的是模拟根据查询条件从服务器上查询相关的报表信息,由于这里是为了演示所以直接返回了Report对象,只是作为实例演示,Report是作为报表对象的抽象,没有任何的数据字段;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-24
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace UnittestDemo
 9 {
10     using System;
11
12     public class ReportAnalyse
13     {
14         public bool Analyse(DateTime dt)
15         {
16             ServiceReport.QueryReport(string.Format("State={0}", 1));
17             return true;
18         }
19     }
20 }

View Code

这是一个实例类,用来对远程返回的表达进行分析,就好比一个业务一个数据访问,只不过这里的数据访问大部分情况下我们都会使用静态类来实现;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-24
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace UnittestDemo
 9 {
10     using System;
11     public class AppStart
12     {
13         public static void MainStart()
14         {
15             ReportAnalyse analyse = new ReportAnalyse();
16             bool result = analyse.Analyse(DateTime.Now);
17
18             if (result)
19             {
20                 //
21             }
22             else
23             {
24                 //
25             }
26         }
27     }
28 }

View Code

这个就是程序调用的地方,用来模拟程序运行时的入口,可以当成是Application Layer中的Facade对象;

其实这里就能看出来我在2.1】小结中说的“单一职责”设计原则,我已经将数据访问代码在ReportAnalyse中使用了,其实这里是不对的,应该是在外部装载好然后传入ReportAnalyse中才对,才符合单一职责设计原则,当然这里不是讲它,所以不扯了;

我们假设上面的代码已经完成了对Report对象的分析了,下面我们需要对代码进行单元测试,主要是两个类ReportAnalyse、ServiceReport,我们先从ReportAnalyse类开始吧;

【单元测试】

创建基本的单元测试项目,然后记得引用被测试项目,最后新建一个用来测试ReportAnalyse类的单元测试文件;

 1 using System;
 2 using Microsoft.VisualStudio.TestTools.UnitTesting;
 3 using UnittestDemo;
 4
 5 namespace UnittestDemoUnit
 6 {
 7     [TestClass]
 8     public class ReportAnalyseUnitTest
 9     {
10         [TestMethod]
11         public void ReportAnalyse_Analyse_UnitTest()
12         {
13             ReportAnalyse testReportAnalyse = new ReportAnalyse();
14             bool result = testReportAnalyse.Analyse(DateTime.Now);
15
16             Assert.IsTrue(result);
17         }
18     }
19 }

View Code

写上很简单的测试用例,这里的主要目的不是怎么写测试用例,也不是怎么测试代码,这里的目的是如何进行单元测试、重构等迭代的过程,所以如何写用例不是重点,这里直接带过了;

图3:

如果没有问题的话,这个单元测试用例肯定是过的,因为没有其他什么逻辑,很简单的两行代码;看起来一起很好,没有问题,单元测试也通过了,这个时候我们放心的去做其他的功能了,但是过了几天发现自己的ReportAnalyse单元测试突然不过了,后来检查发现有人改了ServiceReport实现,原本从本地直接实例化的Report现在需要配置过后才能使用,也就是说你这个时候测试不了你的代码了,以为你的ReportAnalyse会随时受到ServiceReport的影响,但是这个问题如果在运行时是无所谓的,毕竟在产线上都是配置好的;

这个时候就会是牵一发而动全身的困境,因为我们的代码是面向实现编程的,也就是说耦合度很高,这个时候我们需要根据需要对ServiceReport进行适当的重构,当然重构的首要目标就是将它与任何实现脱耦;

下面我们将ServiceReport提取出一个接口,然后通过IOC的方式动态的注入进来就实现了完全的脱耦;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-24
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace UnittestDemo
 9 {
10     using System;
11
12     public class ReportAnalyse
13     {
14         IServiceReport serviceReport;
15
16         public ReportAnalyse(IServiceReport serviceReport)
17         {
18             this.serviceReport = serviceReport;
19         }
20
21         public bool Analyse(DateTime dt)
22         {
23             serviceReport.QueryReport(string.Format("State={0}", 1));
24             return true;
25         }
26     }
27 }

View Code

这里的构造函数当然不是直接实例化的,需要使用相关的IOC框架做支撑;我们看一下上面的代码很简洁,依赖IServiceReport接口,这个时候我们再回过头来对单元测试进行简单的修改来适应可以持续重构的代码;

为了使代码好测试点,我修改了一下Analyse方法;

图4:

画红线的部分在我们没有进行重构之前是会随着ServiceReport的变化而变化的,但是被我们抽象成接口之后就变的很容易测试了,我们自己可以任何控制它的返回值;

图5:

单元测试的代码有一点变化,从构造函数传入的IServiceReport接口已经被Mock过了,其实这是单元测试框架的一中,.NET本身提供的Fakes框架也是很不错的,会给出所有后台的自动生成的模拟代码,而且跟VisualStudioIDE是结合的,很不错;

这个时候我们就可以控制IServiceReport接口的任何行为,我们只有将实现换成接口才能使Mock有机会插入逻辑;

按照这样的单元测试用例,那么用例代码是过不去的,因为我返回了一个null类型的Report对象,这里你就完全可以控制它人会的任何值,所以你的单元测试类不会受到任何外界的干扰,从而使得你的代码具有可测试性;

到目前为止文章的中心已经讲到,我们也看到一个简单的示例,如何从面向接口编程中找到理由这么设计,其实也就是说面向接口编程就会使得类具有可测试性;单元测试与重构是一直持续下去的过程,代码每天都有人在维护,每天都有人在使用单元测试用例,它们之间形成了一个良好的迭代关系;

图6:

这样持续下去代码始终保持一个很稳定的状态,重构过后的代码通过单元测试进行验证,新加入的功能也可以使用单元测试进行实时验证;

2.2.1】LINQ表达式对单元测试的影响

LINQ我们用的还是蛮多的,它对于集合的处理是相当不错的,写起来很顺手,思维也比较连贯;但是LINQ对于单元测试来说需要在编写的时候要注意,不能过于太长,如果太长很难进行测试,就是代码覆盖到了也很难做到100%覆盖率,所以如果我们有两个嵌套以上的建议还是分成两个独立的方法,这样代码就很容易测试了,就算以后改到了也不怕会影响其他的逻辑;

一个很好的建议就是将LINQ的表达式通过方法来返回,方法里面就好比是规约一样的工厂,将具体的LINQ表达式放入一个统一的地方管理;

 

总结:其实我对单元测试、重构也只是一点了解而已,只不过最近对它的理解深入了一点,所以写出来算是对项目的一个总结,觉得还是有很大的参考价值的;任何一个新东西,在我们没有去学习研究它的时候觉得很一般,其实真正去研究了学习了会发现真的很让人吃惊,任何一个东西都会有存在的价值,就看我们是否需要用;很多项目包括我之前的公司长期再维护一个已经无法再维护的项目,就是因为缺乏重构、测试所以变成今天的局面,用我们公司领导的一句话说,将变成公司的“技术债务”,迟早是需要换的;其实慢慢的也就变成了公司的一个巨大的资源消耗点、累赘;

 

示例代码地址:http://files.cnblogs.com/wangiqngpei557/UnittestDemo.zip

 

作者:王清培

出处:http://www.cnblogs.com/wangiqngpei557/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

时间: 2024-08-31 20:19:24

.NET项目开发—浅谈面向接口编程、可测试性、单元测试、迭代重构(项目小结)的相关文章

.NET简谈面向接口编程

过程式的开发方式已逐渐退出大众的眼线,随之而来的是各种各样的高抽象的开发模式:我们不得不承认在没有设计模式的时候,我们很难总结出有价值的开发模型,便于以后重复使用和推广:面向对象的流行,让我们开发人员重新站在一个高的起点来看待软件模型,抽象固然是好事,但是也给初学者带来了迷惑,将软件中的东西都想成很简单的封装,我们只需要调用就行,这样越来越多的开发人员开始慢慢的往上浮,有一定编程经验和感触的人,能够明白我所说的浮,也算是给初学者提个醒吧. 1: 2: 我们将计算机系统抽象层三个层次,我们做应用层

一起谈.NET技术,.NET简谈面向接口编程

过程式的开发方式已逐渐退出大众的眼线,随之而来的是各种各样的高抽象的开发模式:我们不得不承认在没有设计模式的时候,我们很难总结出有价值的开发模型,便于以后重复使用和推广:面向对象的流行,让我们开发人员重新站在一个高的起点来看待软件模型,抽象固然是好事,但是也给初学者带来了迷惑,将软件中的东西都想成很简单的封装,我们只需要调用就行,这样越来越多的开发人员开始慢慢的往上浮,有一定编程经验和感触的人,能够明白我所说的浮,也算是给初学者提个醒吧. 1: 2:  我们将计算机系统抽象层三个层次,我们做应

.NET“.NET研究”简谈面向接口编程

过程式的开发方式已逐渐退出大众的眼线,随之而来的是各种各样的高抽象的开发模式:我们不得不承认在没有设计模式的时候,我们很难总结出有价值的开发模型,便于以后重复使用和推广:面向对象的流行,让我们开发人员重新站在一个高的起点来看待软件模型,抽象固然是好事,但是也给初学者带来了迷惑,将软件中的东西都想成很简单的封装,我们只需要调用就行,这样越来越多的开发人员开始慢慢的往上浮,有一定编程经验和感触的人,能够明白我所说的浮,也算上海闵行企业网站设计与制作是给初学者提个醒吧. 1: 2:  我们将计算机系

.NET项目开发—浅谈面向对象的纵横向关系、多态入口,单元测试(项目小结)

阅读目录: 1.开篇介绍 2.使用委托消除函数串联调用 2.1.使用委托工厂转换两个独立层面的对象 3.多态入口(面向对象继承体系是可被扩展的) 4.多态的受保护方法的单元测试(Protected成员的单元测试) 1]开篇介绍 一如既往,这篇文章是我最近在工作中总结出的一点小小的经验,特此写出来与大家分享,因为我觉得日常开发中这些点点滴滴很有用: 2]使用委托消除函数串联调用 在一般的函数调用情况下,我们都习惯性的将参数传入到某个被调用的方法,这可能就是我们考虑调用方法的惯用思维,但是现在的C#

浅谈如何提高编程效率?

浅谈如何提高编程效率?1.提高工作经验 经验来自实践.平时多阅读一些技能方面的书籍和来自各网站上的优秀文章.如果说,一本书就是一个台阶,那么在人的一生中将有千万道台阶等着我去跨越.每跨越一步台阶,将得到不可估量的财富,而下一步台阶,又将带我步入一个新的境界,获取新的知识. 看到学到做到.平时有时间多看看大牛写的代码,多看看开源的项目并参与一些开源项目的编码工作.2.和大牛.勤奋的人一起工作 和大牛有经验的程序猿一起工作.和勤奋的人一起共事.永远不要相信"你改变不了环境,但可以改变自已."

理解面向接口编程,面向接口编程的优势,以及对系统设计的影响

问题描述 希望各位大神不吝赐教... 解决方案 解决方案二:一个人写代码无所谓,不要接口也可以(当然,某些框架非要你定义接口,这种就没办法)小系统也无所谓,随便写接口这种东西有价值的场景在:大系统,需要多人协作的系统,多人之间工作有交际,需要有调用与被调用的关系,工作开始的时候先把接口定义好,再各自实现,工作效率比较高.某些设计模式里需要用到接口.比如你将系统里的部分可以重用的功能抽取出来,但是这种重用功能里有部分又有不同,那么就可以采用定义接口的方式,把需要自定义的部分暴露出去,公共的部分抽取

浅谈面向业务的信息安全审计系统(1)

[51CTO.com 综合报道]近些年来, IT系统发展很快,企业对IT系统的依赖程度也越来越高,就一个网络信息系统而言,我们不仅需要考虑一些传统的安全问题,比如防黑客.防病毒.防垃圾邮件.防后门.防蠕虫等, 但是,随着信息化程度的提高, 各类业务系统也变得日益 复杂,对 业务系统的防护也变得越来越重要,非传统领域的安全治理也变得越来越重要.根据最 新的统计资料,给企业造成的严重攻击中70%是来自于组织中的内部人员,因此,针对业务系统的信息安全治理成为一道难题,审计应运而生.一.为什么需要 面向

浅谈C语言编程中程序的一些基本的编写优化技巧_C 语言

大概所有学习C语言的初学者,都被前辈说过,C语言是世界上接近最速的编程语言,当然这并不是吹牛,也并不是贬低其他语言,诚然非C语言能写出高速度的代码,但是C语言更容易写出高速的程序(高速不代表高效),然而再好的工具,在外行人手中也只能是黯淡没落. 对于现代编译器,现代CPU而言,我们要尽量迎合CPU的设计(比如架构和处理指令的方式等),虽然编译器是为程序员服务,并且在尽它最大的能力来优化程序员写出的代码,但是毕竟它还没有脱离电子的范畴,如果我们的代码不能让编译器理解,编译器无法帮我们优化代码,那么

浅谈javascript函数式编程_javascript技巧

函数式编程,属于编程范式的一种 1 函数是第一公民,可以返回值,也可以作为其他函数的参数 //console是一个函数 function con(v){ console.log(v) } // execute 也是一个函数 function execute(fn){ fn(1) } //将con函数作为参数传进execute函数 execute(con) // 1 2 接近自然语言的写法   晓池吃完饭然后就去洗澡 可以表现为eat().bathe() // 吃饭函数 function eat(