.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
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,如需转载请自行联系原作者




相关文章
|
敏捷开发 前端开发 测试技术
测试之道:重构你的测试策略 - 测试金字塔模型
测试之道:重构你的测试策略 - 测试金字塔模型
966 118
|
人工智能 测试技术 项目管理
测试不再碎片化:AI智能体平台「项目资料套件」功能上线!
在实际项目中,需求文档分散、整理费时、测试遗漏等问题常困扰测试工作。霍格沃兹推出AI智能体测试平台全新功能——项目资料套件,可将多个关联文档打包管理,并一键生成测试用例,提升测试完整性与效率。支持套件创建、文档关联、编辑删除及用例生成,适用于复杂项目、版本迭代等场景,助力实现智能化测试协作,让测试更高效、更专业。
|
测试技术 UED 开发者
性能测试报告-用于项目的性能验证、性能调优、发现性能缺陷等应用场景
性能测试报告用于评估系统性能、稳定性和安全性,涵盖测试环境、方法、指标分析及缺陷优化建议,是保障软件质量与用户体验的关键文档。
|
Java 测试技术 容器
Jmeter工具使用:HTTP接口性能测试实战
希望这篇文章能够帮助你初步理解如何使用JMeter进行HTTP接口性能测试,有兴趣的话,你可以研究更多关于JMeter的内容。记住,只有理解并掌握了这些工具,你才能充分利用它们发挥其应有的价值。+
1855 23
|
SQL 安全 测试技术
2025接口测试全攻略:高并发、安全防护与六大工具实战指南
本文探讨高并发稳定性验证、安全防护实战及六大工具(Postman、RunnerGo、Apipost、JMeter、SoapUI、Fiddler)选型指南,助力构建未来接口测试体系。接口测试旨在验证数据传输、参数合法性、错误处理能力及性能安全性,其重要性体现在早期发现问题、保障系统稳定和支撑持续集成。常用方法包括功能、性能、安全性及兼容性测试,典型场景涵盖前后端分离开发、第三方服务集成与数据一致性检查。选择合适的工具需综合考虑需求与团队协作等因素。
2536 24
|
Java 测试技术 Spring
简单学Spring Boot | 博客项目的测试
本内容介绍了基于Spring Boot的博客项目测试实践,重点在于通过测试驱动开发(TDD)优化服务层代码,提升代码质量和功能可靠性。案例详细展示了如何为PostService类编写测试用例、运行测试并根据反馈优化功能代码,包括两次优化过程。通过TDD流程,确保每项功能经过严格验证,增强代码可维护性与系统稳定性。
527 0
|
人工智能 数据可视化 测试技术
UAT测试排程工具深度解析:让验收测试不再失控,项目稳稳上线
在系统交付节奏加快的背景下,“测试节奏混乱”已成为项目延期的主因之一。UAT测试排程工具应运而生,帮助团队结构化拆解任务、清晰分配责任、实时掌控进度,打通需求、测试、开发三方协作闭环,提升测试效率与质量。本文还盘点了2025年热门UAT工具,助力团队选型落地,告别靠表格和群聊推进测试的低效方式,实现有节奏、有章法的测试管理。
|
SQL 测试技术
除了postman还有什么接口测试工具
最好还是使用国内的接口测试软件,其实国内替换postman的软件有很多,这里我推荐使用yunedit-post这款接口测试工具来代替postman,因为它除了接口测试功能外,在动态参数的支持、后置处理执行sql语句等支持方面做得比较好。而且还有接口分享功能,可以生成接口文档给团队在线浏览。
907 2
|
存储 JSON API
Python测试淘宝店铺所有商品接口的详细指南
本文详细介绍如何使用Python测试淘宝店铺商品接口,涵盖环境搭建、API接入、签名生成、请求发送、数据解析与存储、异常处理等步骤。通过具体代码示例,帮助开发者轻松获取和分析淘宝店铺商品数据,适用于电商运营、市场分析等场景。遵守法规、注意调用频率限制及数据安全,确保应用的稳定性和合法性。
|
监控 API 开发工具
Socket.IO介绍,以及怎么连接测试Socket.IO接口?
Socket.IO 是一个用于浏览器和服务器间实时双向通信的库,支持低延迟消息传递、跨平台运行及自动重连。文章介绍了其特点与调试需求,并详细说明如何使用 Apifox 工具创建、连接、发送/接收 Socket.IO 事件,以及团队协作和调试技巧。掌握这些技能可提升实时应用开发效率与质量。

热门文章

最新文章