jmockit浅析

1.概述

这里只是了解一下jmockit的基本原理,不涉及太多细节。

 

基本上来说,jmockit的工作原理是通过asm修改原有class的字节码,然后通过jdk的instrument机制替换现有class的内容,达到mock方法或者属性的目的。在测试完成后再恢复原有class。

 

2.分析

2.1 使用

 

先看一个简单的使用例子:

 

public class JmockitTest1 {

	@Test
	public void testMockUp() {
		new MockUp<Foo>() {
			@Mock
			public void say() {
				System.out.println("I'm the mock one");
			}
		};
		Foo foo = new Foo();
		System.out.print("testMockUp:");
		foo.say();
	}

	@Test
	public void testReal() {
		Foo foo = new Foo();
		System.out.print("testReal:");
		foo.say();
	}
}

class Foo {
	public void say() {
		System.out.println("I'm the real one");
	}

}
 

打印出的内容是:

 

testMockUp:I'm the mock one
testReal:I'm the real one

 

2.2 过程分析

 

以上代码的核心在于new MockUp的时候,会调用

 

Mockit.setUpMock(classToMock, this);
 

这个方法里面根据注解取得所有需要mock的方法

 

ublic static void setUpMock(Class<?> realClass, Object mock)
   {
      Class<?> mockClass = mock.getClass();
      new RedefinitionEngine(realClass, mock, mockClass).redefineMethods();
   }

然后生成新的字节码modifiedClassFile,并调用redefineMethods

 private void redefineMethods(boolean forStartupMock)
   {
      if (mockMethods.getMethodCount() > 0 || mockingConfiguration != null) {
         byte[] modifiedClassFile = modifyRealClass(forStartupMock);
         redefineMethods(mockMethods.getMockClassInternalName(), modifiedClassFile, !forStartupMock);
      }
   }

  redefineMethods方法做两件事情:

   1. 修改方法

   2. 注册,以供方法结束时改回来

 

 public void redefineMethods(String mockClassInternalName, byte[] modifiedClassfile, boolean register)
   {
      redefineMethods(modifiedClassfile);

      if (register) {
         addToMapOfRedefinedClasses(mockClassInternalName, modifiedClassfile);
      }
   }

 其中,修改方法中本质上调用的是

 

private void redefineMethods(byte[] modifiedClassfile)
   {
      redefineMethods(new ClassDefinition(realClass, modifiedClassfile));
   }


   public static void redefineMethods(ClassDefinition... classDefs)
   {
      try {
         Startup.instrumentation().redefineClasses(classDefs);
      }
   	  ...
   }

  这里就是利用了jdk的Instrumentation.redefineClasses细节可以参考其javadoc,意思就是修改了当前加载的这个类的方法定义(不过以及在执行中的方法不受影响).所以它可以如此神奇的本质原因还是jdk提供了修改class的接口。

 

再来看测试完成后如何恢复:

之前因为注册了修改过的类,所以在BlockJUnit4ClassRunnerDecorator.executeTest结束时会调用

 

 finally {
            TestRun.resetExpectationsOnAnnotatedMocks();
            savePoint.rollback();
            TestRun.exitNoMockingZone();
         }

  public void rollback()
   {
      restoreClassesTransformedAfterSavepoint();
      restoreClassesRedefinedAfterSavepoint();
      TestRun.getMockClasses().getRegularMocks().removeInstances(previousMockInstancesCount);
   }

 restoreClassesRedefinedAfterSavepoint这根据类名从文件中重新加载类的byte[],并修改回去 

 

 public void restoreOriginalDefinition(Class<?> aClass)
   {
      realClass = aClass;
      byte[] realClassFile = new ClassFile(aClass, false).getBytecode();
      redefineMethods(realClassFile);
   }

 
最后啰嗦一句instrument是怎么初始化的

事实上jmockit通过junit会调用:

 

package org.junit.runner;
public abstract class Runner implements Describable {
   static
   {
      if ("1.6 1.7".contains(System.getProperty("java.specification.version"))) {
         mockit.internal.startup.Startup.initializeIfNeeded();
      }
   }
}

实际调用:
  public void initializeAccordingToJDKVersion()
   {
      String jarFilePath = discoverPathToJarFile();

      if (Startup.jdk6OrLater) {
         new JDK6AgentLoader(jarFilePath).loadAgent();
      }
      else if ("1.5".equals(Startup.javaSpecVersion)) {
         throw new IllegalStateException(
            "JMockit has not been initialized. Check that your Java 5 VM has been started " +
            "with the -javaagent:" + jarFilePath + " command line option.");
      }
      else {
         throw new IllegalStateException("JMockit requires a Java 5 VM or later.");
      }
   }

 testng也是类似的初始化方法。其实整个代码可读性非常好,这里的解释语句都有点多余。

你可能感兴趣的:(jmock)