第三节(作为一个重构技术来使用mockobject) Using mock objects as a refactoring technique
很多人习惯上认为单元测试应该完全透明而且不应该改变runtime code 来简化测试。这个观点是错误的。
单元测试时runtime code 的一级用户而且应该和其他的用户受到同样的对待。如果你的代码对测试的使用来说没有弹性,那么你应该修改你的代码.
据个例子,你怎样看待下面的代码?
[...]
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
[...]
public class DefaultAccountManager implements AccountManager
{
private static final Log LOGGER =
LogFactory.getLog(AccountManager.class);
//注释一
public Account findAccountForUser(String userId)
{
LOGGER.debug("Getting account for user [" + userId + "]");
ResourceBundle bundle =
PropertyResourceBundle.getBundle("technical");
//注释二
String sql = bundle.getString("FIND_ACCOUNT_FOR_USER");
// Some code logic to load a user account using JDBC
[...]
}
[...]
}
注释一:你获得一个由LogFactory产生的log对象
注释二:使用PropertyResourceBundle来获得一个sql命令。
你认为这个代码好吗?我们来看研究两点,他们都与代码的扩展性和灵活性有关。第一个问题是:不可能来采用不同的Log对象,因为它是在类里面产生的。比如,在测试的时候,你或许想要使用一个Log来do nothing,但是你不能。
一般的规则是:一个像这样的类应该能够使用无论什么样的Log对象。这个类的目的是为了展现JDBC logic而不是来记日至。
同样的论述也适用于PropertyResourceBundle.他看起来现在很正常,但是当你决定采用xml来存贮配置的时候会发生什么呢?同样,这个类不是用来处理使用什么样实现的。
一个有效的设计策略是移植一个对象到其他对象时不做改变。外围对象的选择应该在调用的更高曾控制。基本上,当你转移到调用曾,决定用logger或者配置
应该被推到上一层。这种策略提供了代码的扩展型而且能够处理变化。变化是永恒的
1,简单重构(Easy refactoring):
为了是域对象能够传播而重构所有的代码可能就是浪费时间。为了一个单元测试,你可能不准备重构你的代码
。幸运的是,有一个简单的重构技术可以让你保留你原来的接口但却允许他在域对象(或许并没有产生)中传播。做为证明,让我们来看看如何重构DefaultAccountManager这个类。
public class DefaultAccountManager implements AccountManager
{
private Log logger;
private Configuration configuration;
//注释1
public DefaultAccountManager()
{
this(LogFactory.getLog(DefaultAccountManager.class),
new DefaultConfiguration("technical"));
}
public DefaultAccountManager(Log logger,
Configuration configuration)
{
this.logger = logger;
this.configuration = configuration;
}
public Account findAccountForUser(String userId)
{
this.logger.debug("Getting account for user ["
+ userId + "]");
this.configuration.getSQL("FIND_ACCOUNT_FOR_USER");
// Some code logic to load a user account using JDBC
[...]
}
[...]
}
注意注释1,你把PropertyResourceBundle类交换为一个新的Configuration接口。这使得代码更有弹性因为他引入了一个接口(很容易模拟),而且Configuration的实现可以很灵活的实现(包括使用resource bundles)。现在的设计更好了因为你可以重复使用DefaultAccountManager类,在其中可以任意实现Log和Configuration
接口(如果你使用构造函数,他带有两个参数)。这个类能在外部调用。同时,你没有破坏已经存在的接口,因为你只不过添加了一个构造函数。你保留了原始的默认构造函数----仍然初始化logger和configuration两个成员变量通过默认的实现(implementations)。
通过这个重构,你已经提供了一个trapdoor为了控制来自测试中的域对象,你保留了向后兼容性而且铺垫了通向未来的很容易的重构之路。调用类可以按自己的方式使用新的构造函数。
2 更具扩展性的代码(Allowing more flexible code)
在上一节中我们介绍了一个很有名的模式---控制反转(IOC),主要意思是具体化来自外部类/方法的所有
的域对象,而且传递所有的东西给他。不是由类来实例化对象,而是实例传递到类上(通常通过接口)。
在例子中,他的意思是 传递Log和Configuration对象到DefaultAccountManager类上,DefaultAccountManager没有限制什么样的实例可以传进来或者他们怎样被构造的。他只知道他们实现了Log和Configuration接口。
IOC----控制反转的定义:
应用ioc 模式于类中意思是去掉对一个类的初始化--当这个类没有立即被需要--而且代替的是传递必要的实例。这些实例可能会被传递通过一种特殊的构造方法,通过一个setter或者作为一个方法的参数。调用代码来正确的设置这些被调用的类域对象成为了一种职责。
下面我们来看看为findAccountByUser方法写一个测试将会使如何的简单。
public void testFindAccountByUser()
{
MockLog logger = new MockLog();
//注释一
MockConfiguration configuration = new MockConfiguration();
configuration.setSQL("SELECT * [...]");
//注释2
DefaultAccountManager am =new DefaultAccountManager(logger, configuration);
//注释3
Account account = am.findAccountForUser("1234");
// Perform asserts here
[...]
}
}
注释1 使用了一个实现了Log接口但是没有做任何事情的模拟logger。
注释2 产生了一个MockConfiguration 实例并且返回一个给定的sql查询--当调用Configuration.getSQL的时候。
注释3 产生了一DefaultAccountManager实例,你可以测试,传给他log和configuration实例
你已经能够完全控制你的logging和configuration(来自外部代码)来进行测试,在代码中。因此,你的代码是扩展性良好而且比较灵活的(允许任何logging和configutation的实现在此应用)你将看到更多的这些代码的重构在这一章节和下面的章节中。
最后要值得注意的是,如果你先写你的测试代码,你将会自动设计你的代码具有扩展性。扩展性是一个关键
当你写单元测试的时候。如果你测试在先,你将不会招致需要以后为了扩展性而重构你的代码。
相关推荐
Mock Object是一种在单元测试中模拟真实对象的行为和交互的技术,它允许测试人员控制对象间的依赖关系,从而在隔离环境中测试单个组件。这样可以避免复杂的真实系统依赖,提高测试的效率和准确性。 首先,Eclipse是...
在单元测试中,有时我们需要隔离被测试的代码,避免依赖于其他组件或外部服务。Mockito通过模拟这些依赖,我们可以控制它们的行为并检查调用情况。例如,`when(mockObject.someMethod()).thenReturn(someValue)`可以...
在这个名为 "Mockito-and-Junit:Java Mock deafferents方法的示例存储库" 的项目中,我们主要会探讨如何结合 Mockito 和 JUnit 来进行高效的单元测试,特别是关于模拟方法(Mock methods)的应用。 1. **Mockito ...
阅读指引: 第一部分:Junit精粹 第1章:带着你为一个简单的对象创建测试。在此过程中介绍了单元测试的好处,... 第7章:展示了Mock object方法,这是一种让你可以把代码从周围领域对象隔离出来的测试的方法。 ......
本培训资料将引导你深入理解Maven的项目管理机制,掌握JUnit的测试方法,以及如何使用Mock工具进行单元测试。通过学习这些内容,你将能更高效地进行Java开发,提高代码质量,并为团队合作打下坚实基础。
在复杂系统中,常常需要隔离测试目标代码,这时可以使用Mockito等库创建模拟对象,代替真实依赖。例如,通过`@Mock`注解创建mock对象,然后使用`when().thenReturn()`设定模拟行为。 九、持续集成与TestNg JUnit...
3. **设置 Expectations**:在测试方法中,通过 `when()` 方法指定 mock 对象的期望行为,例如:`when(mockObject.someMethod()).thenReturn(someValue)`。 4. **执行测试**:然后,在测试代码中使用 mock 对象,...
在软件开发过程中,单元测试是确保代码质量的重要环节,而模拟对象(Mock Object)在单元测试中扮演着至关重要的角色。EasyMock是一款广泛使用的Java模拟框架,它允许开发者创建和控制模拟对象,以便在测试中隔离被...
这通常需要使用依赖注入(Dependency Injection)技术,使测试时可以替换掉真实的依赖,用模拟对象(Mock Object)或存根(Stub)来控制它们的行为。 4. 断言:断言是测试用例的核心,它用来检查被测试代码的结果...
3. Mockito:Mockito是一个模拟框架,用于创建和配置mock对象,帮助我们在测试中隔离被测代码。通过mock,我们可以控制方法的行为和返回值,避免了对复杂外部系统的依赖。 4. Spring Boot Testing:如果你的项目...
在实际项目中,jMock常与其他测试框架如JUnit或TestNG结合使用,形成一套完整的测试解决方案。例如,可以使用jMock定义模拟对象的期望,然后在JUnit的测试方法中执行这些期望并验证结果。 总结来说,jMock 2.6.0-...
通过这样的例子,开发者可以学习如何有效地组织测试代码,如何使用Mockito来模拟复杂的依赖关系,以及如何利用JUnit进行测试驱动开发。理解并熟练掌握JUnit和Mockito,不仅有助于提高代码质量,还能使开发过程更加...
根据给定的信息,本文将详细解析“单元测试一条龙”文档中的关键知识点,涵盖Java基本概念、Junit单元测试、Feed4Junit参数化扩展、JUnitPerf单元性能测试以及使用SeleniumJunit进行自动化测试等内容。 ### 面向...
7. **Mock对象**:JUnit可以与其他mocking框架如Mockito结合,用于模拟协作对象,隔离被测试代码。 8. **测试监听器**:JUnit允许添加测试监听器,扩展测试过程,如报告生成、性能度量等。 在“jar”标签下,我们...
在传统的单元测试中,我们通常会使用诸如Mockito这样的工具来创建模拟对象,以隔离被测试代码和其依赖。JMock不仅提供了模拟对象的功能,还能记录和验证对象的方法调用,这对于测试驱动开发(TDD)和行为驱动开发...
在实际项目中,我们可能会遇到需要模拟对象来隔离测试的情况。JUnit4提供了`Mockito`库支持,通过`@Mock`和`@InjectMocks`注解,我们可以创建并注入模拟对象,以确保测试只关注被测代码的行为,而不是依赖的外部组件...
5. **Mockito框架**:为了隔离测试,可能会使用`Mockito`来模拟依赖服务,比如当认证服务依赖于数据库时,可以通过mock数据库来简化测试。 6. **Hamcrest匹配器**:用于断言预期结果,如`assertThat()`方法配合`...
本篇文章将探讨如何结合`FactoryBean`与Jmock库来实现动态Mock类的注入,以便于进行单元测试。 首先,我们需要了解`FactoryBean`的基本用法。`FactoryBean`的`getObject()`方法负责返回一个由工厂生产的对象,而...