|
JMockit 是用以帮助开发人员编写测试程序的一组工具和API,该项目完全基于 Java 5 SE 的 java.lang.instrument 包开发,内部使用 ASM 库来修改Java的Bytecode。
When writing Unit Tests, it's often necessary to mock classes. There are several good frameworks to do this, such as EasyMock. This works fine in most cases, but it's possible to run into problems when using certain designs.
EasyMock (the same is true for most other mocking frameworks) can only mock public, non static or final methods. In most cases this is not a problem, it will fit most designs. It can be a problem however if you have code that uses, for example, a lot of static methods. It might be an option to refactor this (a lot of static methods might be an indication of bad design), but what if you use classes from external libraries?
JMockit is a small framework that can help out in such cases. It allows you to dynamically replace methods with new definitions. This is based on the Java 1.5 Instrumentation framework. The cool thing about JMockit is that it works with almost every design. It allows you to redefine private, static and final methods. Even no-arg constructors can be redefined.
At the project i'm currently working on we had some trouble figuring out what can and what can't be redefined, and how the method definitions should look like. I've written a small class and a JUnit testcase that redefines all possible methods from the class under test.
在单元测试中,经常需要进行一些mock操作。现在已经有了一些比较不错的框架在做这些事情,比如:EasyMck,他在大多数情况下运行良好,但是对于某些结构的设计却显得无能为力。
EasyMock等众多的mock框架仅能mock一些public,non static or final的方法,在大多数情况下这并没有什么问题,他可以处理大多数的问题,但是当测试的代码包含了一些静态方法,可能就让问题变得难以解决,有一种选择即是重构他(过多的静态方法可能预示着这并不是一个很好的设计),但是当你使用外部引用库所提供的方法,问题又该如何解决呢?
JMockit是一个能帮我们解决以上问题的轻量级框架,他允许你动态的改变已有的方法,这主要基于java 1.5的Instrumentation框架,这样便可以使得JMockit能够适应几乎所有的设计。他允许你重定义private,static and final方法,甚至是no-arg constructors都能够并轻易的重定义。
在实际的项目中有些方法可以重定义而有些不行,为了更好的说明如何对方法进行重定义,下面有一个简单的类和对应测试代码的demo,他尽可能考虑到了几乎所有的情况,供大家方便的学习。(理解不透的地方,希望大家指正)
ClassToMock :
public class ClassToMock {
private String memberToSet;
private static String staticMember;
static {
staticMember = "Static initialized";
}
public ClassToMock() {
this.memberToSet = "Member set by original constructor";
}
public ClassToMock(String value) {
this.memberToSet = "Member set by original constructor";
}
public String publicMethod() {
return "Original public method";
}
protected String protectedMethod() {
return "Original protected method";
}
String defaultMethod() {
return "Original default method";
}
public String methodThatUsesPrivateMethod() {
return privateMethod();
}
private String privateMethod() {
return "Original private method";
}
public String getMemberToSet() {
return memberToSet;
}
public String getStaticMember() {
return staticMember;
}
}
ClassToMockTest:
import junit.framework.TestCase;
import mockit.Mockit;
public class ClassToMockTest extends TestCase {
private ClassToMock mockedClass;
public static class Replacement {
static {
}
public Replacement() {
}
public Replacement(String test) {
}
public String publicMethod() {
return "Replaced public method";
}
public String protectedMethod() {
return "Replaced protected method";
}
public String defaultMethod() {
return "Replaced default method";
}
public String privateMethod() {
return "Replaced private method";
}
}
protected void setUp() throws Exception {
Mockit.redefineMethods(ClassToMock.class, Replacement.class);
mockedClass = new ClassToMock("test");
}
protected void tearDown() throws Exception {
Mockit.restoreAllOriginalDefinitions();
}
/**
* Public methods can be replaced
*/
public void testReplacePublic() {
assertEquals("Replaced public method", mockedClass.publicMethod());
}
/**
* Protected methods can be replaced.
* The replacement method should be declared public however
*/
public void testReplaceProtected() {
assertEquals("Replaced protected method", mockedClass.protectedMethod());
}
/**
* Package accessable methods can be replaced
* The replacement method should be declared public however
*/
public void testReplaceDefault() {
assertEquals("Replaced default method", mockedClass.defaultMethod());
}
/**
* Private methods can be replaced
* The replacement method should be declared public however
*/
public void testReplacePrivate() {
assertEquals("Replaced private method", mockedClass
.methodThatUsesPrivateMethod());
}
/**
* Non-default constructors can be replaced
* Your mock definition must have a default constructor however
*/
public void testReplaceConstructor() {
assertEquals(null, mockedClass.getMemberToSet());
}
/**
* Default constructors <b>can't</b> be replaced
*/
public void testReplaceDefaultConstructor() {
mockedClass = new ClassToMock();
assertEquals("Member set by original constructor", mockedClass
.getMemberToSet());
}
/**
* Static initializers <b>can't</b> be replaced
*/
public void testReplaceStaticBlock() {
assertEquals("Static initialized", mockedClass.getStaticMember());
}
} |
|