Javassist API 与 Junit
Javassist API with Junit
我试图在每次测试前更改一些第三方 class 定义,以模拟不同的结果。我必须使用 javassist 之类的东西,因为有时由于访问修饰符而无法扩展 classes。这是我尝试结合使用 javassist 和 junit 的示例:
public class SimulatedSession extends SomeThirdParty {
private boolean isJoe = false;
public SimulatedSession(final boolean isJoe) {
this.isJoe = isJoe;
}
@Override
public void performThis() {
final ClassPool classPool = ClassPool.getDefault();
final CtClass internalClass = classPool.get("some.package.Class");
final CtMethod callMethod = internalClass.getDeclaredMethod("doThis");
if (isJoe) {
callMethod.setBody("{System.out.println(\"Joe\");}");
} else {
callMethod.setBody("{System.out.println(\"mik\");}");
}
internalClass.toClass();
}
}
@Test
public void firstTest() {
SimulatedSession toUse = new SimulatedSession(false);
// do something with this object and this flow
}
@Test
public void nextTest() {
SimulatedSession toUse = new SimulatedSession(true);
// do something with this object and this flow
}
如果我 运行 每个单独测试,我可以 运行 代码就好了。当我 运行 他们使用单元套件时,一个接一个地测试,我得到 "frozen class issue"。为了解决这个问题,我正在查看这个 post,但是,我必须承认我不确定如何使用不同的 class 池来解决问题。
也许是另一种方法。我们有一个类似的问题,因为我们曾经嘲笑过一个依赖项——我们无法重置它。所以我们做了以下事情:在每次测试之前,我们用我们的模拟替换 'live' 实例。测试后,我们恢复活动实例。所以我建议您为每个测试替换第三方代码的修改实例。
@Before
public void setup()
{
this.liveBeanImpl = (LiveBean) ReflectionTools.getFieldValue(this.beanToTest, "liveBean");
ReflectionTools.setFieldValue(this.beanToTest, "liveBean", new TestStub());
}
@After
public void cleanup()
{
ReflectionTools.setFieldValue(this.beanToTest, "liveBean", his.liveBeanImpl);
}
setFieldValue 如下所示:
public static void setFieldValue(Object instanceToModify, String fieldName, Object valueToSet)
{
try
{
Field declaredFieldToSet = instanceToModify.getClass().getDeclaredField(fieldName);
declaredFieldToSet.setAccessible(true);
declaredFieldToSet.set(instanceToModify, valueToSet);
declaredFieldToSet.setAccessible(false);
}
catch (Exception exception)
{
String className = exception.getClass().getCanonicalName();
String message = exception.getMessage();
String errorFormat = "\n\t'%s' caught when setting value of field '%s': %s";
String error = String.format(errorFormat, className, fieldName, message);
Assert.fail(error);
}
}
因此,如果您为每个测试重置实施,您的测试可能会通过。你明白了吗?
您当前的代码将尝试将相同的 class 加载到同一个 ClassLoader
中两次,这是被禁止的,对于给定的 [=14=,您只能加载一次 class ].
为了让你的单元测试通过,我必须:
- 创建我自己的临时
ClassLoader
,它将能够加载 some.package.Class
(出于测试目的,我将其替换为 javassist.MyClass
)并且将以这样的方式实现首先尝试在父级的 CL 之前从它加载 class。
- 将我自己的
ClassLoader
设置为上下文 ClassLoader
。
- 更改
SimulatedSession#performThis()
的代码,以便能够获取通过此方法创建的 class 实例并调用 internalClass.defrost()
以防止“冻结 class 问题".
- 通过反射调用方法
doThis()
来确保我有不同的输出,方法是使用 SimulatedSession#performThis()
返回的 class 实例来确保 class 使用已加载我的 ClassLoader
.
所以假设我的 class javassist.MyClass
是:
package javassist;
public class MyClass {
public void doThis() {
}
}
修改后的方法SimulatedSession#performThis()
:
public Class<?> performThis() throws Exception {
final ClassPool classPool = ClassPool.getDefault();
final CtClass internalClass = classPool.get("javassist.MyClass");
// Prevent the "frozen class issue"
internalClass.defrost();
...
return internalClass.toClass();
}
单元测试:
// The custom CL
private URLClassLoader cl;
// The previous context CL
private ClassLoader old;
@Before
public void init() throws Exception {
// Provide the URL corresponding to the folder that contains the class
// `javassist.MyClass`
this.cl = new URLClassLoader(new URL[]{new File("target/classes").toURI().toURL()}){
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
try {
// Try to find the class for this CL
return findClass(name);
} catch( ClassNotFoundException e ) {
// Could not find the class so load it from the parent
return super.loadClass(name, resolve);
}
}
};
// Get the current context CL and store it into old
this.old = Thread.currentThread().getContextClassLoader();
// Set the custom CL as new context CL
Thread.currentThread().setContextClassLoader(cl);
}
@After
public void restore() throws Exception {
// Restore the context CL
Thread.currentThread().setContextClassLoader(old);
// Close the custom CL
cl.close();
}
@Test
public void firstTest() throws Exception {
SimulatedSession toUse = new SimulatedSession(false);
Class<?> c = toUse.performThis();
// Invoke doThis() by reflection
Object o2 = c.newInstance();
c.getMethod("doThis").invoke(o2);
}
@Test
public void nextTest() throws Exception {
SimulatedSession toUse = new SimulatedSession(true);
Class<?> c = toUse.performThis();
// Invoke doThis() by reflection
Object o2 = c.newInstance();
c.getMethod("doThis").invoke(o2);
}
输出:
mik
Joe
看看retransformer。这是我为 运行 测试编写的基于 Javassist 的库,就像这样。它比使用原始 Javassist 更简洁。
我试图在每次测试前更改一些第三方 class 定义,以模拟不同的结果。我必须使用 javassist 之类的东西,因为有时由于访问修饰符而无法扩展 classes。这是我尝试结合使用 javassist 和 junit 的示例:
public class SimulatedSession extends SomeThirdParty {
private boolean isJoe = false;
public SimulatedSession(final boolean isJoe) {
this.isJoe = isJoe;
}
@Override
public void performThis() {
final ClassPool classPool = ClassPool.getDefault();
final CtClass internalClass = classPool.get("some.package.Class");
final CtMethod callMethod = internalClass.getDeclaredMethod("doThis");
if (isJoe) {
callMethod.setBody("{System.out.println(\"Joe\");}");
} else {
callMethod.setBody("{System.out.println(\"mik\");}");
}
internalClass.toClass();
}
}
@Test
public void firstTest() {
SimulatedSession toUse = new SimulatedSession(false);
// do something with this object and this flow
}
@Test
public void nextTest() {
SimulatedSession toUse = new SimulatedSession(true);
// do something with this object and this flow
}
如果我 运行 每个单独测试,我可以 运行 代码就好了。当我 运行 他们使用单元套件时,一个接一个地测试,我得到 "frozen class issue"。为了解决这个问题,我正在查看这个 post,但是,我必须承认我不确定如何使用不同的 class 池来解决问题。
也许是另一种方法。我们有一个类似的问题,因为我们曾经嘲笑过一个依赖项——我们无法重置它。所以我们做了以下事情:在每次测试之前,我们用我们的模拟替换 'live' 实例。测试后,我们恢复活动实例。所以我建议您为每个测试替换第三方代码的修改实例。
@Before
public void setup()
{
this.liveBeanImpl = (LiveBean) ReflectionTools.getFieldValue(this.beanToTest, "liveBean");
ReflectionTools.setFieldValue(this.beanToTest, "liveBean", new TestStub());
}
@After
public void cleanup()
{
ReflectionTools.setFieldValue(this.beanToTest, "liveBean", his.liveBeanImpl);
}
setFieldValue 如下所示:
public static void setFieldValue(Object instanceToModify, String fieldName, Object valueToSet)
{
try
{
Field declaredFieldToSet = instanceToModify.getClass().getDeclaredField(fieldName);
declaredFieldToSet.setAccessible(true);
declaredFieldToSet.set(instanceToModify, valueToSet);
declaredFieldToSet.setAccessible(false);
}
catch (Exception exception)
{
String className = exception.getClass().getCanonicalName();
String message = exception.getMessage();
String errorFormat = "\n\t'%s' caught when setting value of field '%s': %s";
String error = String.format(errorFormat, className, fieldName, message);
Assert.fail(error);
}
}
因此,如果您为每个测试重置实施,您的测试可能会通过。你明白了吗?
您当前的代码将尝试将相同的 class 加载到同一个 ClassLoader
中两次,这是被禁止的,对于给定的 [=14=,您只能加载一次 class ].
为了让你的单元测试通过,我必须:
- 创建我自己的临时
ClassLoader
,它将能够加载some.package.Class
(出于测试目的,我将其替换为javassist.MyClass
)并且将以这样的方式实现首先尝试在父级的 CL 之前从它加载 class。 - 将我自己的
ClassLoader
设置为上下文ClassLoader
。 - 更改
SimulatedSession#performThis()
的代码,以便能够获取通过此方法创建的 class 实例并调用internalClass.defrost()
以防止“冻结 class 问题". - 通过反射调用方法
doThis()
来确保我有不同的输出,方法是使用SimulatedSession#performThis()
返回的 class 实例来确保 class 使用已加载我的ClassLoader
.
所以假设我的 class javassist.MyClass
是:
package javassist;
public class MyClass {
public void doThis() {
}
}
修改后的方法SimulatedSession#performThis()
:
public Class<?> performThis() throws Exception {
final ClassPool classPool = ClassPool.getDefault();
final CtClass internalClass = classPool.get("javassist.MyClass");
// Prevent the "frozen class issue"
internalClass.defrost();
...
return internalClass.toClass();
}
单元测试:
// The custom CL
private URLClassLoader cl;
// The previous context CL
private ClassLoader old;
@Before
public void init() throws Exception {
// Provide the URL corresponding to the folder that contains the class
// `javassist.MyClass`
this.cl = new URLClassLoader(new URL[]{new File("target/classes").toURI().toURL()}){
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
try {
// Try to find the class for this CL
return findClass(name);
} catch( ClassNotFoundException e ) {
// Could not find the class so load it from the parent
return super.loadClass(name, resolve);
}
}
};
// Get the current context CL and store it into old
this.old = Thread.currentThread().getContextClassLoader();
// Set the custom CL as new context CL
Thread.currentThread().setContextClassLoader(cl);
}
@After
public void restore() throws Exception {
// Restore the context CL
Thread.currentThread().setContextClassLoader(old);
// Close the custom CL
cl.close();
}
@Test
public void firstTest() throws Exception {
SimulatedSession toUse = new SimulatedSession(false);
Class<?> c = toUse.performThis();
// Invoke doThis() by reflection
Object o2 = c.newInstance();
c.getMethod("doThis").invoke(o2);
}
@Test
public void nextTest() throws Exception {
SimulatedSession toUse = new SimulatedSession(true);
Class<?> c = toUse.performThis();
// Invoke doThis() by reflection
Object o2 = c.newInstance();
c.getMethod("doThis").invoke(o2);
}
输出:
mik
Joe
看看retransformer。这是我为 运行 测试编写的基于 Javassist 的库,就像这样。它比使用原始 Javassist 更简洁。