阅读目录:

  • 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*==============================================================================
 * 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是作为报表对象的抽象,没有任何的数据字段;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*==============================================================================
 * 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}", 1));
            return true;
        }
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*==============================================================================
 * 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类的单元测试文件;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的方式动态的注入进来就实现了完全的脱耦;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*==============================================================================
 * 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}", 1));
            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

本文转自 王清培 51CTO博客,原文链接:http://blog.51cto.com/wangqingpei557/1282332,如需转载请自行联系原作者

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

  1. 浅谈“面向接口编程” :依赖反转 IOC 与 DI (容器控制反转与依赖注入)

    IOC:Inversion of Control Containers and the Dependency Injection "Water" by Andy Huan, Mal ...

  2. unity 项目开发——浅谈设计模式的六大原则(一)

    目录 前言 首先,六大原则是谁? 其次,为什么需要学习这六大原则? 正文 一.单一职责原则 示例: 因此我们需要进行拆分,根据具体的职能可将其具体拆分如下: Unity 单一职责原则 二.开闭原则 U ...

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

    阅读目录: 1.开篇介绍 2.使用委托消除函数串联调用 2.1.使用委托工厂转换两个独立层面的对象 3.多态入口(面向对象继承体系是可被扩展的) 4.多态的受保护方法的单元测试(Protected成员 ...

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

    阅读目录: 1.开篇介绍 2.使用委托消除函数串联调用 2.1.使用委托工厂转换两个独立层面的对象 3.多态入口(面向对象继承体系是可被扩展的) 4.多态的受保护方法的单元测试(Protected成员 ...

  5. Hubble A/B test平台前端项目开发浅谈

    前言 现在Hubble abtest平台已经正式上线了,至于技术实现方案我在其中一篇文章做了一些分享(查看react技术栈实践),本文主要分享下一些开发过程中的遇到问题思考总结,以及简单介绍下我们平台 ...

  6. 面向接口编程的优点_为什么我们要面向接口编程

    到底面向?编程 面向过程编程( ProcedureOriented.简称 PO) 和 面向对象编程( ObjectOriented.简称 OO) 我们一定听过,然而实际企业级开发里受用更多的一种编程思 ...

  7. java继续_Java中消除实现继续和面向接口编程

    在匆忙之际理清消除实现继续和面向接口编程这样两个大题目可不是一件轻易的事情,尤其考虑到自身的熟悉水平.坦白的说,这又是一篇"炒冷饭"的文章,但这"冷饭"又确实不 ...

  8. 那些年搞不懂的高深术语——依赖倒置•控制反转•依赖注入•面向接口编程...

    那些年,空气中仿佛还能闻到汉唐盛世的余韵,因此你决不允许自己的脸上有油光,时刻保持活力.然而,你一定曾为这些"高深术语"感到过困扰--依赖倒置•控制反转•依赖注入•面向接口编程.也 ...

  9. 高深术语——依赖倒置•控制反转•依赖注入•面向接口编程

    今天,我将带领你以一种全新的高清视角进入奇妙的编程世界,领略涵泳在这些"高深术语"中的活泼泼的地气,以及翩跹于青萍之末的云水禅心. 高聚合·低耦合 简短:管理好自己(内聚),但是有 ...

  10. 【转】那些年搞不懂的高深术语——依赖倒置•控制反转•依赖注入•面向接口编程

    作者:在好 链接:https://www.zhihu.com/question/31021366/answer/102239756 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载 ...

最新文章

  1. oracle 10g磁盘管理,Oracle 10g UNDO表空间过大导致磁盘空间不足的解决
  2. 软件驱动安装在docker_docker 安装软件
  3. numpy 深复制 切片创建视图
  4. C# list删除 另外list里面的元素_在Python 中 List 操作 9种例子详细了解
  5. qt 在label上以光标位置进行缩放_缩放|位移|渐变简单动画
  6. seo按天扣费系统_网站seo优化多少钱,SEO快速排名按天扣费怎么样
  7. Swing 显示良好JPanel保存为图片
  8. ICRA2021会议-----SLAM方向汇总
  9. Android调用手机摄像头
  10. PHP佳音高清图片上传下载源码
  11. 微信表白代码,单身汪们还不快收藏!
  12. mysql pxc 使用_PXC使用介绍
  13. 麒麟案例 | 南浔电商领头羊+麒麟计划,碰撞出了企业下一个10年增长之路!
  14. 软件可靠性测试概念与应用
  15. Python为什么这些年在编程语言排行榜上一直上升?告诉你11个原因
  16. SpringBoot图片上传失败
  17. 对辛辛那提原始数据集(ims)数据划分正常,内圈故障,滚动体故障,外圈故障(python代码),可作为自己故障诊断模型实验的另一个数据集(因为CWRU普遍而言,准确度和被使用频率已经极高)
  18. html 在线打开pdf文件怎么打开,在HTML中打开pdf文件怎么打开
  19. 使用Git将代码上传到Gitee仓库
  20. truetype字体怎么转换成普通字体_一种TrueType字体渲染方法与流程

热门文章

  1. 中国计算机管理软件学院,中国管理软件学院官方网站
  2. matlab数字信号处理 王彬 pdf,MATLAB数字信号处理
  3. mysql视图的更新 条件_MySQL进阶16 - 视图的创建/修改/删除/更新--可更新性的不适用条件...
  4. mysql安装zip怎么安装可视化_Mysql zip压缩版安装步骤总结
  5. Git 本地分支关联远程分支
  6. [Err] 1136 - Column count doesn't match value count at row 35
  7. python使用长ping命令_在Python中调用Ping命令,批量IP的方法
  8. error LNK2001: 无法解析的外部符号 _ft_sdf_renderer_class/ _ft_bitmap_sdf_renderer_class
  9. 使用超时加锁:pthread_mutex_timedlock
  10. 下载mp4v2代码的链接