[英]How to monitor the invocation of methods in abstract class using java agent and ASM?
我要做的是监控JUnit 4种测试方法的调用。 我必须自己这样做的原因是:我需要记录每个测试方法执行过程中执行的类。 所以我需要在测试方法中插入一些指令,以便我知道测试何时开始/结束以及那些记录的类是由哪个测试实体执行的。 所以我需要自己过滤测试方法。
实际上,我这样做的原因与问题无关,因为我没有在标题中提到“JUnit”、“test”。 在其他类似情况下,该问题仍然可能是一个问题。
我的情况是这样的:
public abstract class BaseTest {
@Test
public void t8() {
assert new C().m() == 1;
}
}
public class TestC extends BaseTest{
// empty
}
我还修改了 Surefire 的成员argLine
,以便在 Surefire 启动新的 JVM 进程以执行测试时附加我的代理(预主模式)。
在我的代理 class 中:
public static void premain(String args, Instrumentation inst){
isPreMain = true;
agentArgs = args;
log("args: " + args);
parseArgs(args);
inst.addTransformer(new TestTransformer(), true);
}
我的变压器 class:
public class TestTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
log("TestTransformer: transform: " + className);
...
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
RecordClassAdapter mca = new RecordClassAdapter(cw, className);
cr.accept(mca, 0);
return cw.toByteArray();
}
}
在我的 ClassVisitor 适配器 class 中:
class RecordClassAdapter extends ClassVisitor {
...
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
mv = new RecordMethodAdapter (...);
return mv;
}
}
在我的 MethodVisitor 适配器 class 中:
class RecordMethodAdapter extends MethodVisitor {
public void visitCode() {
mv.visitCode();
if (isTestMethod){
mv.visitLdcInsn(methodName);
mv.visitMethodInsn(INVOKESTATIC, MyClass, "entityStarted",
"(Ljava/lang/String;)V", false);
}
}
}
可悲的是,我发现抽象 class 不会进入transform
方法,因此我无法检测t8
方法。 TestC
应该作为测试 class 执行,但我永远无法监视TestC.t8
的调用。
有几个机会通过 JUnit API 将日志记录注入测试。 不需要仪器。
对于一个非常简单的设置:
public class BaseTest {
@Test
public void t8() {
System.out.println("Running test "+getClass().getName()+".t8() [BaseTest.t8()]");
}
@Test
public void anotherMethod() {
System.out.println("Running test "
+getClass().getName()+".anotherMethod() [BaseTest.anotherMethod()]");
}
}
public class TestC extends BaseTest {
@Rule
public TestName name = new TestName();
@Before
public void logStart() throws Exception {
System.out.println("Starting test "+getClass().getName()+'.'+name.getMethodName());
}
@After
public void logEnd() throws Exception {
System.out.println("Finished test "+getClass().getName()+'.'+name.getMethodName());
}
}
这将打印
Starting test class TestC.t8
Running test TestC.t8() [BaseTest.t8()]
Finished test class TestC.t8
Starting test class TestC.anotherMethod
Running test TestC.anotherMethod() [BaseTest.anotherMethod()]
Finished test class TestC.anotherMethod
您也可以实施自己的规则。 例如特设:
public class TestB extends BaseTest {
@Rule
public TestRule notify = TestB::decorateTest;
static Statement decorateTest(Statement st, Description d) {
return new Statement() {
@Override public void evaluate() throws Throwable {
System.out.println("Starting test "+d.getClassName()+"."+d.getMethodName());
st.evaluate();
System.out.println("Finished test "+d.getClassName()+"."+d.getMethodName());
}
};
}
}
或者作为可以通过单线插入测试 class 的可重用规则
public class LoggingRule implements TestRule {
public static final LoggingRule INSTANCE = new LoggingRule();
private LoggingRule() {}
@Override
public Statement apply(Statement base, Description description) {
Logger log = Logger.getLogger(description.getClassName());
log.setLevel(Level.FINEST);
Logger.getLogger("").getHandlers()[0].setLevel(Level.FINEST);
String clName = description.getClassName(), mName = description.getMethodName();
return new Statement() {
@Override
public void evaluate() throws Throwable {
log.entering(clName, mName);
String result = "SUCCESS";
try {
base.evaluate();
}
catch(Throwable t) {
result = "FAIL";
log.throwing(clName, mName, t);
}
finally {
log.exiting(clName, mName, result);
}
}
};
}
}
使用起来很简单
public class TestB extends BaseTest {
@Rule
public LoggingRule log = LoggingRule.INSTANCE;
}
另一种方法是实现自定义测试运行器。 这允许将行为应用于整个测试套件,因为测试套件也是通过运行器实现的。
public class LoggingSuiteRunner extends Suite {
public LoggingSuiteRunner(Class<?> klass, RunnerBuilder builder)
throws InitializationError {
super(klass, builder);
}
@Override
public void run(RunNotifier notifier) {
notifier.addListener(LOG_LISTENER);
try {
super.run(notifier);
} finally {
notifier.removeListener(LOG_LISTENER);
}
}
static final RunListener LOG_LISTENER = new RunListener() {
public void testStarted(Description d) {
System.out.println("Starting test "+d.getClassName()+"."+d.getMethodName());
}
public void testFinished(Description d) {
System.out.println("Finished test "+d.getClassName()+"."+d.getMethodName());
}
public void testFailure(Failure f) {
Description d = f.getDescription();
System.out.println("Failed test "+d.getClassName()+"."+d.getMethodName()
+": "+f.getMessage());
};
};
}
这可能会应用于整个测试套件,即仍然从BaseTest
继承测试方法,您可以使用
@RunWith(LoggingSuiteRunner.class)
@SuiteClasses({ TestB.class, TestC.class })
public class TestA {}
public class TestB extends BaseTest {}
public class TestC extends BaseTest {}
这将打印
Starting test TestB.t8
Running test TestB.t8() [BaseTest.t8()]
Finished test TestB.t8
Starting test TestB.anotherMethod
Running test TestB.anotherMethod() [BaseTest.anotherMethod()]
Finished test TestB.anotherMethod
Starting test TestC.t8
Running test TestC.t8() [BaseTest.t8()]
Finished test TestC.t8
Starting test TestC.anotherMethod
Running test TestC.anotherMethod() [BaseTest.anotherMethod()]
Finished test TestC.anotherMethod
这些只是建议,建议研究 API,它允许更多。 要考虑的另一点是,根据您用于启动测试的方法(您提到了 maven 插件),可能支持在此处添加全局RunListener
,而无需更改测试类。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.