为旧版代码创建存根–测试技术6

任何阅读此博客的人都可能已经意识到,目前我正在开发一个包含大量旧代码的项目,这些旧代码庞大,扩展且编写时从未进行过任何测试。

在使用此遗留代码时,有一个行为异常的类非常普遍,整个团队都一次又一次地犯错。

为了保护这一罪恶,我将其称为X先生,尽管它的真名是SitePropertiesManager,因为它管理网站的属性。 表现不佳是因为:

  • 打破单一责任主体
  • 使用了由getInstance()工厂方法汇总的单例模式,
  • 有一个init()方法,必须在其他任何方法之前调用它,
  • 通过直接访问数据库而不是使用DAO加载其数据,
  • 使用复杂的地图来存储其数据,
  • 访问文件系统以缓存数据库返回的数据
  • 有一个计时器来决定何时更新其缓存。
  • 是在泛型之前编写的,具有大量多余的findXXXX()方法。
  • 没有实现接口,
  • 使用了大量的复制和粘贴程序。

这使得在编写新代码的单元测试时很难创建存根,并使旧代码杂乱无章:

SitePropertiesManager propman = SitePropertiesManager.getInstance();

该博客介绍了处理尴尬字符的方法,并演示了如何为它们创建存根,同时消除了Singleton模式的影响。 与以前的“测试技术”博客一样,我的演示代码也基于“地址”网络应用示例 。

在本系列的其他博客中,我一直在演示如何测试AddressService ,这个博客也没有什么不同。 但是,在这种情况下, AddressService必须加载站点属性并决定是否返回地址,但是在查看之前,我首先需要使用写得不好的SitePropertiesManager来使用。 但是,我不拥有该代码,因此我编写了一个特技双重版本,该版本打破了我能想到的许多规则。 我不会在这里让您感到厌烦,因为SitePropertiesManager的所有源代码都可以在以下位置找到:git://github.com/roghughe/captaindebug.git

如上所述,在这种情况下, AddressService使用站点属性来确定是否启用了它。 如果是,它将发送回一个地址对象。 我还要假装AddressService是一些使用站点属性静态工厂方法的旧代码,如下所示:

public Address findAddress(int id) {logger.info("In Address Service with id: " + id);Address address = Address.INVALID_ADDRESS;if (isAddressServiceEnabled()) {address = addressDao.findAddress(id);address = businessMethod(address);}logger.info("Leaving Address Service with id: " + id);return address;}private boolean isAddressServiceEnabled() {SitePropertiesManager propManager = SitePropertiesManager.getInstance();return new Boolean(propManager.findProperty("address.enabled"));}

在驯服这种类型的类时,他要做的第一件事是停止使用getInstance()获取保持和一个对象,将其从上述方法中删除,并开始使用依赖项注入。 必须至少调用一次getInstance() ,但这可以在程序的启动代码中进行。 在Spring的世界中,解决方案是将行为不佳的类包装在Spring FactoryBean实现中,该类成为应用程序中getInstance()的唯一位置–至少对于新代码/增强代码而言。

public class SitePropertiesManagerFactoryBean implementsFactoryBean {private static SitePropertiesManager propsManager = SitePropertiesManager.getInstance();@Overridepublic SitePropertiesManager getObject() throws Exception {return propsManager;}@Overridepublic Class getObjectType() {return SitePropertiesManager.class;}@Overridepublic boolean isSingleton() {return true;}
}

现在可以将其自动连接到AddressService类中:

@Autowiredvoid setPropertiesManager(SitePropertiesManager propManager) {this.propManager = propManager;}

但是,这些更改并不意味着我们可以为AddressService编写一些适当的单元测试,它们只是准备基础。 下一步是为SitePropertiesManager提取接口,使用eclipse可以轻松实现。

public interface PropertiesManager {public abstract String findProperty(String propertyName);public abstract String findProperty(String propertyName, String locale);public abstract List findListProperty(String propertyName);public abstract List findListProperty(String propertyName, String locale);public abstract int findIntProperty(String propertyName);public abstract int findIntProperty(String propertyName, String locale);}

在转移到接口时,我们还需要在Spring配置文件中手动配置SitePropertiesManager的实例,以便Spring知道将哪个类连接到哪个接口:

<beans:bean id="propman" class="com.captaindebug.siteproperties.SitePropertiesManager" />

我们还需要使用限定符更新AddressService的 @Autowired批注:

@Autowired@Qualifier("propman")void setPropertiesManager(PropertiesManager propManager) {this.propManager = propManager;}

通过一个接口,我们现在可以轻松编写一个简单的SitePropertiesManager存根:

public class StubPropertiesManager implements PropertiesManager {private final Map propMap = new HashMap();public void setProperty(String key, String value) {propMap.put(key, value);}@Overridepublic String findProperty(String propertyName) {return propMap.get(propertyName);}@Overridepublic String findProperty(String propertyName, String locale) {throw new UnsupportedOperationException();}@Overridepublic List findListProperty(String propertyName) {throw new UnsupportedOperationException();}@Overridepublic List findListProperty(String propertyName, String locale) {throw new UnsupportedOperationException();}@Overridepublic int findIntProperty(String propertyName) {throw new UnsupportedOperationException();}@Overridepublic int findIntProperty(String propertyName, String locale) {throw new UnsupportedOperationException();}
}

有了存根,很容易为使用存根并与数据库和文件系统隔离的AddressService编写单元测试

public class AddressServiceUnitTest {private StubAddressDao addressDao;private StubPropertiesManager stubProperties;private AddressService instance;@Beforepublic void setUp() {instance = new AddressService();stubProperties = new StubPropertiesManager();instance.setPropertiesManager(stubProperties);}@Testpublic void testAddressSiteProperties_AddressServiceDisabled() {/* Set up the AddressDAO Stubb for this test */Address address = new Address(1, "15 My Street", "My Town", "POSTCODE","My Country");addressDao = new StubAddressDao(address);instance.setAddressDao(addressDao);stubProperties.setProperty("address.enabled", "false");Address expected = Address.INVALID_ADDRESS;Address result = instance.findAddress(1);assertEquals(expected, result);}@Testpublic void testAddressSiteProperties_AddressServiceEnabled() {/* Set up the AddressDAO Stubb for this test */Address address = new Address(1, "15 My Street", "My Town", "POSTCODE","My Country");addressDao = new StubAddressDao(address);instance.setAddressDao(addressDao);stubProperties.setProperty("address.enabled", "true");Address result = instance.findAddress(1);assertEquals(address, result);}
}

所有这些都是很笨拙的,但是如果您不能提取接口会发生什么呢? 我将其保存另一天...

参考: Captain Debug博客上的 JCG合作伙伴 提供的用于遗留代码的存根-测试技术6

相关文章 :

  • 测试技巧–不编写测试
  • 端到端测试的滥用–测试技术2
  • 您应该对什么进行单元测试? –测试技术3
  • 常规单元测试和存根–测​​试技术4
  • 使用模拟的单元测试–测试技术5
  • 有关为旧版代码创建存根的更多信息–测试技术7
  • 为什么要编写单元测试–测试技巧8
  • 一些定义–测试技术9
  • 使用FindBugs产生更少的错误代码
  • 在云中开发和测试

翻译自: https://www.javacodegeeks.com/2011/11/creating-stubs-for-legacy-code-testing.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374326.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python性能解决_我们如何发现并解决Python代码中性能下降的问题

Python部落(python.freelycode.com)组织翻译&#xff0c;禁止转载&#xff0c;欢迎转发。 作者&#xff1a;Omer Lachish 最近&#xff0c;我们已经开始使用RQ库代替Celery库作为我们的任务运行引擎。第一阶段&#xff0c;我们只迁移了那些不直接进行查询工作的任务。这些任务包…

图灵机器人调用数据恢复_机器人也能撩妹?python程序员自制微信机器人,替他俘获女神芳心...

机器人也有感情还记得王传君饰演的《星语心愿之再爱》这部电影吗&#xff1f;王传君饰演的天才程序员“王鹏鹏”因工作原因不能陪伴照顾身在异地的女朋友“林亦男”&#xff0c;呆萌宅男“王鹏鹏”开发出一款以自己为原型的“王鹏鹏8.0”程序去陪伴异地恋的女友&#xff0c;后来…

Spark排错与优化

一. 运维 1. Master挂掉,standby重启也失效 Master默认使用512M内存&#xff0c;当集群中运行的任务特别多时&#xff0c;就会挂掉&#xff0c;原因是master会读取每个task的event log日志去生成spark ui&#xff0c;内存不足自然会OOM&#xff0c;可以在master的运行日志中看到…

在MySQL上使用带密码的GlassFish JDBC安全性

我在该博客上最成功的文章之一是有关在GlassFish上使用基于表单的身份验证来建立JDBC安全领域的文章 。 对这篇文章的一些评论使我意识到&#xff0c;要真正使它安全&#xff0c;应该做的还很多。 开箱即用的安全性 图片&#xff1a; TheKenChan &#xff08; CC BY-NC 2.0 &a…

利用python进行数据分析_利用python进行数据分析复现(1)

&#xfeff;一直以来&#xff0c;都想学习python数据分析相关的知识&#xff0c;总是拖拖拉拉&#xff0c;包括这次这个分享也是。《利用python进行数据分析 第2版》是一次无意之间在简书上看到的一个分享&#xff0c;我决定将很详细。一直都想着可以复现一下。但总有理由&…

C语言代码规范(七)#define

#define 宏定义的使用 #define MAX(x, y) ( ((x) > (y)) ? (x) : (y) ) #define MIN(x, y) ( ((x) < (y)) ? (x) : (y) ) 在宏定义中要把参数用括号扩起来( ((x) > (y)) ? (x) : (y) )。 因为宏只是简单的文本替换&#xff0c;如果不注意&#xff0c;很容…

http 二进制_浅谈HTTP协议

HTTP一、HTTP协议http协议&#xff0c;是超文本传输协议&#xff0c;此协议是基于TCP/IP的协议&#xff0c;是互联网上应用最为广泛的一直网络协议是一种无状态协议&#xff0c;默认端口为80,。设计HTTP的最初目的是为了提供一种发布和接受HTML页面的方法。通过HTTP或者HTTPS协…

linux redis客户端_为什么单线程Redis能那么快?

我们通常说&#xff0c;Redis 是单线程&#xff0c;主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的&#xff0c;这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能&#xff0c;比如持久化、异步删除、集群数据同步等&#xff0c;其实是由额外的线…

C语言开发笔记(七)const和指针

const修饰变量是常用的&#xff0c;不容易犯错&#xff0c;而const和指针一起使用时很容易混淆。 (一)const int *p #include <stdio.h>int main(void) {int a 10;int b 20;const int *p &a;*p b;return 0; } const在int *的左侧&#xff0c;即指针指向内容为…

ubuntu php 无法执行exec_利用webhook使php项目自动部署

php中文网最新课程每日17点准时技术干货分享1.先来讲一下自动部署的原理&#xff0c;一般在我们push代码的时候&#xff0c;可以自动请求webhook中设置的url&#xff0c;完成一次请求与响应。那么只要我们设置的url地址请求的php文件内容是执行命令行git push命令&#xff0c;则…

C语言开发笔记(八)static

在C语言中&#xff0c;static有3个作用&#xff1a; &#xff08;1&#xff09;在函数体&#xff0c;一个被声明为静态的变量在这一函数体内被调用的过程中维持其值不变。 #include <stdio.h>void test(void) {static int i 0;printf("%d\n", i); }int main…

ppt修复无法读取_移动硬盘故障分析以及建议修复方法

移动硬盘中存储了大量的重要数据&#xff0c;一旦出现什么问题&#xff0c;会让人急得焦头烂额。换个硬盘倒是件小事&#xff0c;但其中资料、数据的丢失更令人懊恼。而在硬盘使用的过程中&#xff0c;由于使用者一时的不注意&#xff0c;往往就很容易造成意外的问题。接下来&a…

(原创)c#学习笔记04--流程控制01--布尔逻辑03--运算符优先级

转载于:https://www.cnblogs.com/wodehao0808/p/4896018.html

m.2接口和nvme区别_NVMe/SATA SSD有啥不一样?萌新怎么选

随着NAND技术的升级迭代&#xff0c;堆栈层数不断提高使得SSD单位容量成本不断下降&#xff0c;消费级市场基本已经成为了SSD的天下。目前主流的SSD大致有两种接口&#xff0c;分别是M.2和SATA两种类型。NVMe/SATA有啥区别SATA接口的SSD执行的AHCI协议标准&#xff0c;是目前较…

带有NetBeans 7.1 RC 2的WebLogic 12c快速入门

WebLogic服务器12c停运了几天。 它是针对“裸露”的Java开发人员的–花哨的Fusion Middleware东西将继续沿线升至12c。 因此&#xff0c;这基本上是我要运行的版本。 今天&#xff0c;我为您提供了一个最新的NetBeans 7.1&#xff08;RC 2&#xff09;和WebLogic的快速入门 &am…

python爬虫反爬机制_Python Scrapy突破反爬虫机制(项目实践)

对于 BOSS 直聘这种网站&#xff0c;当程序请求网页后&#xff0c;服务器响应内容包含了整个页面的 HTML 源代码&#xff0c;这样就可以使用爬虫来爬取数据。但有些网站做了一些“反爬虫”处理&#xff0c;其网页内容不是静态的&#xff0c;而是使用 JavaScript 动态加载的&…

自定义URL Scheme完全指南

iPhone / iOS SDK 最酷的特性之一就是应用将其自身”绑定”到一个自定义 URL scheme 上&#xff0c;该 scheme 用于从浏览器或其他应用中启动本应用。 注册自定义 URL Scheme 注册自定义 URL Scheme 的第一步是创建 URL Scheme — 在 Xcode Project Navigator 中找到并点击工程…

P6 音频格式—— AAC

目录 前言 01 AAC是什么&#xff1f; 02 为什么需要进行AAC进行音频压缩处理&#xff1f; 03 AAC的特点以及优势 04 AAC格式详解&#xff1a; 4.1. ADIF的数据结构&#xff1a; 4.1.1 ADIF Header具体的表格: 4.2. ADTS的结构&#xff08;重点&#xff09;&#xff1a; …

Android开发笔记——ListView模块、缓存及性能

ListView是Android开发中最常用的组件之一。本文将重点说明如何正确使用ListView&#xff0c;以及使用过程中可能遇到的问题。 ListView开发模块图片缓存可能遇到的问题一、ListView开发模块 从项目实践的角度来看&#xff0c;ListView适合“自底向上”的开发模式&#xff0c;即…

python实现excel筛选功能并输出_python如何实现excel按颜色筛选功能

离岛 2020-07-09 09:37 已采纳 不太了解具体需求&#xff0c;提供一些示例代码和思路供你参考&#xff1a; 整体思路&#xff1a;首先已知excel中的颜色值&#xff0c;根据编码实现颜色筛选的功能 示例&#xff1a; 1、首先安装pip install openpyxl 2、示例代码可以获取Excel中…