在使用此遗留代码时,有一个行为异常的类非常普遍,整个团队都一次又一次地犯错。
为了保护这一罪恶,我将其称为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