.NET项目开发—浅谈面向接口编程、可测试性、单元测试、迭代重构(项目小结)
阅读目录:
- 1.开篇介绍
- 2.迭代测试、重构(强制性面向接口编程,要求代码具有可测试性)
- 2.1.面向接口编程的两个设计误区
- 2.1.1.接口的依赖倒置
- 2.1.2.接口对实体的抽象
- 2.2.迭代单元测试、重构(代码可测试)
- 2.2.1.LINQ表达式对单元测试的影响
- 2.1.面向接口编程的两个设计误区
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】迭代单元测试、重构(代码可测试)
其实这篇文章的主要内容是在这一节,上一节我说了一下我对接口抽象的一点个人看法;这一节我们将通过一个具体的示例来看一下这篇文章的重要内容,看看单元测试如何与持续迭代重构完美结合的,在编写单元测试用例的时候我们将发现代码被逐渐的重构的很优美,面向接口编程再一次被提到一个高度;
在我们编写代码的时候一般情况下无法验证我们的代码好与坏,光凭嘴说也很难断定每个人的设计思路是否完全正确的,所以代码可测试性将成为验证你所编写的代码的质量的一个重要指标;
单元测试与重构将是一个持续迭代的过程,很多人并不太关心重构和单元测试,其实是因为我们大部分情况下在开发一次性的交付的项目而不是持续更新的产品,所以单元测试、重构被我们所忽视,面向接口编程也被我们时而记起也时而忘记,下面我们来看一下如何编写可测试性的代码;
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-24
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/ namespace UnittestDemo
{
using System.Linq.Expressions;
using System; public static class ServiceReport
{
public static Report QueryReport(string queryWhere)
{
return new Report();
}
}
}
这是一个很简单的静态类,主要目的是模拟根据查询条件从服务器上查询相关的报表信息,由于这里是为了演示所以直接返回了Report对象,只是作为实例演示,Report是作为报表对象的抽象,没有任何的数据字段;
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-24
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/ namespace UnittestDemo
{
using System; public class ReportAnalyse
{
public bool Analyse(DateTime dt)
{
ServiceReport.QueryReport(string.Format("State={0}", ));
return true;
}
}
}
这是一个实例类,用来对远程返回的表达进行分析,就好比一个业务一个数据访问,只不过这里的数据访问大部分情况下我们都会使用静态类来实现;
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-24
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/ namespace UnittestDemo
{
using System;
public class AppStart
{
public static void MainStart()
{
ReportAnalyse analyse = new ReportAnalyse();
bool result = analyse.Analyse(DateTime.Now); if (result)
{
//
}
else
{
//
}
}
}
}
这个就是程序调用的地方,用来模拟程序运行时的入口,可以当成是Application Layer中的Facade对象;
其实这里就能看出来我在2.1】小结中说的“单一职责”设计原则,我已经将数据访问代码在ReportAnalyse中使用了,其实这里是不对的,应该是在外部装载好然后传入ReportAnalyse中才对,才符合单一职责设计原则,当然这里不是讲它,所以不扯了;
我们假设上面的代码已经完成了对Report对象的分析了,下面我们需要对代码进行单元测试,主要是两个类ReportAnalyse、ServiceReport,我们先从ReportAnalyse类开始吧;
【单元测试】
创建基本的单元测试项目,然后记得引用被测试项目,最后新建一个用来测试ReportAnalyse类的单元测试文件;
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnittestDemo; namespace UnittestDemoUnit
{
[TestClass]
public class ReportAnalyseUnitTest
{
[TestMethod]
public void ReportAnalyse_Analyse_UnitTest()
{
ReportAnalyse testReportAnalyse = new ReportAnalyse();
bool result = testReportAnalyse.Analyse(DateTime.Now); Assert.IsTrue(result);
}
}
}
写上很简单的测试用例,这里的主要目的不是怎么写测试用例,也不是怎么测试代码,这里的目的是如何进行单元测试、重构等迭代的过程,所以如何写用例不是重点,这里直接带过了;
图3:
如果没有问题的话,这个单元测试用例肯定是过的,因为没有其他什么逻辑,很简单的两行代码;看起来一起很好,没有问题,单元测试也通过了,这个时候我们放心的去做其他的功能了,但是过了几天发现自己的ReportAnalyse单元测试突然不过了,后来检查发现有人改了ServiceReport实现,原本从本地直接实例化的Report现在需要配置过后才能使用,也就是说你这个时候测试不了你的代码了,以为你的ReportAnalyse会随时受到ServiceReport的影响,但是这个问题如果在运行时是无所谓的,毕竟在产线上都是配置好的;
这个时候就会是牵一发而动全身的困境,因为我们的代码是面向实现编程的,也就是说耦合度很高,这个时候我们需要根据需要对ServiceReport进行适当的重构,当然重构的首要目标就是将它与任何实现脱耦;
下面我们将ServiceReport提取出一个接口,然后通过IOC的方式动态的注入进来就实现了完全的脱耦;
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-24
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/ namespace UnittestDemo
{
using System; public class ReportAnalyse
{
IServiceReport serviceReport; public ReportAnalyse(IServiceReport serviceReport)
{
this.serviceReport = serviceReport;
} public bool Analyse(DateTime dt)
{
serviceReport.QueryReport(string.Format("State={0}", ));
return true;
}
}
}
这里的构造函数当然不是直接实例化的,需要使用相关的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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
.NET项目开发—浅谈面向接口编程、可测试性、单元测试、迭代重构(项目小结)的更多相关文章
- (语法基础)浅谈面向切面编程(AOP)
一:前言 面向切面编程是一个很重要的编程思想,想要写出一个便于维护的程序,理解AOP并且能熟练的在实际编程中合理的运用AOP思想是很有必要的 二:AOP的基本概念 基础概念:AOP中文翻译面向切面编程 ...
- .NET项目开发—浅谈面向对象的纵横向关系、多态入口,单元测试(项目小结)
阅读目录: 1.开篇介绍 2.使用委托消除函数串联调用 2.1.使用委托工厂转换两个独立层面的对象 3.多态入口(面向对象继承体系是可被扩展的) 4.多态的受保护方法的单元测试(Protected成员 ...
- 浅谈Windows API编程
WinSDK是编程中的传统难点,个人写的WinAPI程序也不少了,其实之所以难就难在每个调用的API都包含着Windows这个操作系统的潜规则或者是windows内部的运行机制…… WinSDK是编程 ...
- Android开发-浅谈架构(二)
写在前面的话 我记得有一期罗胖的<罗辑思维>中他提到 我们在这个碎片化 充满焦虑的时代该怎么学习--用30%的时间 了解70%该领域的知识然后迅速转移芳草鲜美的地方 像游牧民族那样.原话应 ...
- go 学习笔记之万万没想到宠物店竟然催生出面向接口编程?
到底是要猫还是要狗 在上篇文章中,我们编撰了一则简短的小故事用于讲解了什么是面向对象的继承特性以及 Go 语言是如何实现这种继承语义的,这一节我们将继续探讨新的场景,希望能顺便讲解面向对象的接口概念. ...
- javascript设计模式学习之十七——程序设计原则与面向接口编程
一.编程设计原则 1)单一职责原则(SRP): 这里的职责是指“引起变化的原因”:单一职责原则体现为:一个对象(方法)只做一件事. 事实上,未必要在任何时候都一成不变地遵守原则,实际开发中,因为种种原 ...
- java面向接口编程
在oop中有一种设计原则是面向接口编程,面向接口编程有非常多优点,详细百度一大片.我来谈一下详细的使用中的一些不成熟的见解.! 首先面向接口编程能够消除类之间的依赖关系,使得业务仅仅依赖接口. 这样有 ...
- [转帖]浅谈响应式编程(Reactive Programming)
浅谈响应式编程(Reactive Programming) https://www.jianshu.com/p/1765f658200a 例子写的非常好呢. 0.9312018.02.14 21:22 ...
- Python测试开发-浅谈如何自动化生成测试脚本
Python测试开发-浅谈如何自动化生成测试脚本 原创: fin 测试开发社区 前天 阅读文本大概需要 6.66 分钟. 一 .接口列表展示,并选择 在右边,点击选择要关联的接口,区分是否要登录, ...
随机推荐
- Android存储空间不足的解决办法
安装应用时,有时会出现错误Installation error: INSTALL_FAILED_INSUFFICIENT_STORAGE,这是存储空间不足的错误,这时就只能将应用安装到SD卡,在And ...
- 算法实质【Matrix67】
动态规划 :你追一个MM的时候,需要对该MM身边的各闺中密友都好,这样你追MM这个问题 就分解为对其MM朋友的问题,只有把这些问题都解决了,最终你才能追到MM. 因此,该问题适用于聪明的MM,懂得“看 ...
- java.lang.IllegalStateException: Recursive entry to executePendingTransactions
[解决办法]: 将getFragmentManager改为getChildFragmentManager即可
- Loadrunner中web_find和web_reg_find函数的使用与区别
总结一下Loadrunner中的检查点函数,主要介绍两个函数:web_find()和web_reg_find():这两个函数均用于内容的查找,但两者也有本质的区别,具体介绍如下:一.web_find( ...
- mysql出错:Access denied for user 'root'@'localhost' (using password: YES)
网站链接mysql数据库的时候,连接不上,并报出错误:Access denied for user 'root'@'localhost' (using password:YES) 这是个相当恼火的问题 ...
- Redis-复制
Redis 的复制 (replication) 是一种使用和配置起来非常简单的主从(master-slave)复制,允许 Redis 从服务器成为主服务器的精确副本.以下是关于 Redis 复制的一些 ...
- iCheck.js
一.iCheck:http://www.bootcss.com/p/icheck/ 1.选择一个颜色主题,网上有,有了一个肤色主题,然后就是把这个肤色主题的css文件复制到iCheck文件夹里面 2. ...
- Muzli – 所有你需要的设计灵感都在这
曾几何时,三个家伙一起工作,他们分享各种令人振奋的资源链接.然后,其中一个人有一个想法.一个绝妙的主意,是否可以有一种方式来与他人分享这些链接?因此, Muzli 诞生了.所有你需要的设计灵感这里都有 ...
- 优秀的 HTML5 实战教程,提升你的综合开发能力
HTML5 作为下一代网站开发技术,无论你是一个 Web 开发人员或者想探索新的平台的游戏开发者,都值得去研究.借助尖端功能,技术和 API,HTML5 允许你创建响应性.创新性.互动性以及令人惊叹的 ...
- Chance – 功能强大的 JavaScript 随机数生成类库
Chance 是一个基于 JavaScript 的随机数工具类.可以生成随机数字,名称,地址,域名,邮箱,时间等等,几乎网站中使用的任何形式的内容都能够生成.这个随机数工具可以帮助减少单调的测试数据编 ...