为什么我的 Mockito 内联 JUnit 测试在通过 Ant 运行 时崩溃,并在 IntelliJ IDEA 中成功?
Why do my Mockito-inline JUnit tests crash when run through Ant, and succeed in IntelliJ IDEA?
由于各种原因,我已经开始使用 Mockito 在我的单元测试中模拟一些 classes,并且在我开发的过程中,我让一切正常工作。完成更改后,我推送到我们的构建服务器,发现通过 Ant 运行 时单元测试被破坏。我已经能够在一个简单的精简案例中重现这一点,在该案例中我使用 Mockito 来测试被测 class 依赖项的模拟静态方法。下面我粘贴了源文件、测试文件、Ant 脚本和 Ivy 依赖文件,描述了我正在使用的每个库的版本(此时所有这些都是最新的),然后是详细错误 I看到了。
但是如果我在一个目录中创建这几个文件(src/com/example
下的两个 classes 和 tests/com/example
下的单元测试并将其作为 IntelliJ IDEA 项目打开,使用所有相同的依赖项(包括 JUnit)和 运行 所有测试,它成功了。与标准模块模板的唯一变化是将 JDK 路径和 Java 级别指定为 1.8。那是部分原因让它如此令人费解。就其价值而言,Ant 的行为是相同的,无论是从 IntelliJ IDEA 的内置 Ant 集成中调用,还是单独在命令行上调用。
BusinessClass.java
package com.example;
public class BusinessClass {
public String returnString() {
StaticDependency.DoSomething();
return "Answer";
}
}
StaticDependency.java
package com.example;
public class StaticDependency {
public static void DoSomething() {
throw new RuntimeException("Real code causes side effects I'm isolating from");
}
}
BusinessClassTest.java
package com.example;
import org.junit.Test;
import org.mockito.MockedStatic;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mockStatic;
public class BusinessClassTest {
@Test
public void returnString() {
final BusinessClass classUnderTest = new BusinessClass();
try (final MockedStatic<StaticDependency> ignored = mockStatic(StaticDependency.class)) {
assertEquals("Answer", classUnderTest.returnString());
}
}
}
ivy.xml
<ivy-module version="2.0">
<info organisation="com.example" module="MockitoTest"/>
<dependencies>
<dependency org="junit" name="junit" rev="4.13.2"/>
<!-- Mockito and dependencies -->
<dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
<dependency org="net.bytebuddy" name="byte-buddy-dep" rev="1.12.6"/>
<dependency org="org.objenesis" name="objenesis" rev="3.2"/>
</dependencies>
</ivy-module>
build.xml
<project default="all" xmlns:ivy="antlib:org.apache.ivy.ant">
<property name="lib" location="Lib"/>
<property name="src" location="src"/>
<property name="tests" location="tests"/>
<property name="build-test" location="build-test"/>
<property name="build" location="build"/>
<path id="build.classpath">
<fileset dir="${lib}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="all" depends="clean, resolve, compile, test"/>
<target name="clean">
<delete dir="${build}"/>
<delete dir="${build-test}"/>
<delete dir="${lib}"/>
</target>
<target name="resolve" description="Download dependencies">
<ivy:resolve file="ivy.xml"/>
<mkdir dir="${lib}"/>
<ivy:retrieve pattern="${lib}/[artifact]-[type].[ext]"/>
</target>
<target name="compile" description="Compile the source code">
<mkdir dir="${build}"/>
<javac destdir="${build}">
<src path="${src}"/>
<classpath refid="build.classpath"/>
</javac>
<mkdir dir="${build-test}"/>
<javac destdir="${build-test}">
<src path="${src}"/>
<src path="${tests}"/>
<classpath refid="build.classpath"/>
</javac>
</target>
<target name="test" depends="compile" description="Run all unit tests">
<junit>
<classpath>
<path refid="build.classpath"/>
<path path="${build-test}"/>
</classpath>
<formatter type="plain" usefile="false"/>
<test name="com.example.BusinessClassTest" />
</junit>
</target>
</project>
JUnit 输出
Testsuite: com.example.BusinessClassTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.889 sec
Testcase: returnString took 0.779 sec
Caused an ERROR
Could not modify all classes [class com.example.StaticDependency]
org.mockito.exceptions.base.MockitoException: Could not modify all classes [class com.example.StaticDependency]
at com.example.BusinessClassTest.returnString(Unknown Source)
Caused by: java.lang.IllegalStateException:
Byte Buddy could not instrument all classes within the mock's type hierarchy
This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:
- Compiled by older versions of scalac
- Classes that are part of the Android distribution
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:280)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClassStatic(InlineBytecodeGenerator.java:221)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClassStatic(TypeCachingBytecodeGenerator.java:63)
at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createStaticMock(InlineDelegateByteBuddyMockMaker.java:560)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createStaticMock(InlineByteBuddyMockMaker.java:83)
at org.mockito.internal.util.MockUtil.createStaticMock(MockUtil.java:147)
at org.mockito.internal.MockitoCore.mockStatic(MockitoCore.java:142)
at org.mockito.Mockito.mockStatic(Mockito.java:2164)
at org.mockito.Mockito.mockStatic(Mockito.java:2101)
Caused by: java.lang.AbstractMethodError: org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper.wrap(Lnet/bytebuddy/description/type/TypeDescription;Lorg/objectweb/asm/ClassVisitor;Lnet/bytebuddy/implementation/Implementation$Context;Lnet/bytebuddy/pool/TypePool;Lnet/bytebuddy/description/field/FieldList;Lnet/bytebuddy/description/method/MethodList;II)Lorg/objectweb/asm/ClassVisitor;
at net.bytebuddy.asm.AsmVisitorWrapper$Compound.wrap(AsmVisitorWrapper.java:746)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor.visit(TypeWriter.java:4906)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:569)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:424)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3951)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2213)
at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:224)
at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:123)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3661)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.transform(InlineBytecodeGenerator.java:394)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:276)
事实证明我的问题是我的 ivy.xml
文件中的 byte-buddy-dep
依赖性适得其反。正如 @temp-droid pointed out in a GitHub issue 我在 Mockito 项目中提出的同样的问题,将其替换为 byte-buddy
和 byte-buddy-agent
会起作用。
然后我意识到 Ivy 不要求我明确列出子依赖项(我不知道它知道如何解决它们),所以我能够更新到更简单的 ivy.xml
,它在 Ant 和 IntelliJ 中同样有效:
<ivy-module version="2.0">
<info organisation="com.example" module="MockitoTest"/>
<dependencies>
<dependency org="junit" name="junit" rev="4.13.2"/>
<dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
</dependencies>
</ivy-module>
由于各种原因,我已经开始使用 Mockito 在我的单元测试中模拟一些 classes,并且在我开发的过程中,我让一切正常工作。完成更改后,我推送到我们的构建服务器,发现通过 Ant 运行 时单元测试被破坏。我已经能够在一个简单的精简案例中重现这一点,在该案例中我使用 Mockito 来测试被测 class 依赖项的模拟静态方法。下面我粘贴了源文件、测试文件、Ant 脚本和 Ivy 依赖文件,描述了我正在使用的每个库的版本(此时所有这些都是最新的),然后是详细错误 I看到了。
但是如果我在一个目录中创建这几个文件(src/com/example
下的两个 classes 和 tests/com/example
下的单元测试并将其作为 IntelliJ IDEA 项目打开,使用所有相同的依赖项(包括 JUnit)和 运行 所有测试,它成功了。与标准模块模板的唯一变化是将 JDK 路径和 Java 级别指定为 1.8。那是部分原因让它如此令人费解。就其价值而言,Ant 的行为是相同的,无论是从 IntelliJ IDEA 的内置 Ant 集成中调用,还是单独在命令行上调用。
BusinessClass.java
package com.example;
public class BusinessClass {
public String returnString() {
StaticDependency.DoSomething();
return "Answer";
}
}
StaticDependency.java
package com.example;
public class StaticDependency {
public static void DoSomething() {
throw new RuntimeException("Real code causes side effects I'm isolating from");
}
}
BusinessClassTest.java
package com.example;
import org.junit.Test;
import org.mockito.MockedStatic;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mockStatic;
public class BusinessClassTest {
@Test
public void returnString() {
final BusinessClass classUnderTest = new BusinessClass();
try (final MockedStatic<StaticDependency> ignored = mockStatic(StaticDependency.class)) {
assertEquals("Answer", classUnderTest.returnString());
}
}
}
ivy.xml
<ivy-module version="2.0">
<info organisation="com.example" module="MockitoTest"/>
<dependencies>
<dependency org="junit" name="junit" rev="4.13.2"/>
<!-- Mockito and dependencies -->
<dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
<dependency org="net.bytebuddy" name="byte-buddy-dep" rev="1.12.6"/>
<dependency org="org.objenesis" name="objenesis" rev="3.2"/>
</dependencies>
</ivy-module>
build.xml
<project default="all" xmlns:ivy="antlib:org.apache.ivy.ant">
<property name="lib" location="Lib"/>
<property name="src" location="src"/>
<property name="tests" location="tests"/>
<property name="build-test" location="build-test"/>
<property name="build" location="build"/>
<path id="build.classpath">
<fileset dir="${lib}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="all" depends="clean, resolve, compile, test"/>
<target name="clean">
<delete dir="${build}"/>
<delete dir="${build-test}"/>
<delete dir="${lib}"/>
</target>
<target name="resolve" description="Download dependencies">
<ivy:resolve file="ivy.xml"/>
<mkdir dir="${lib}"/>
<ivy:retrieve pattern="${lib}/[artifact]-[type].[ext]"/>
</target>
<target name="compile" description="Compile the source code">
<mkdir dir="${build}"/>
<javac destdir="${build}">
<src path="${src}"/>
<classpath refid="build.classpath"/>
</javac>
<mkdir dir="${build-test}"/>
<javac destdir="${build-test}">
<src path="${src}"/>
<src path="${tests}"/>
<classpath refid="build.classpath"/>
</javac>
</target>
<target name="test" depends="compile" description="Run all unit tests">
<junit>
<classpath>
<path refid="build.classpath"/>
<path path="${build-test}"/>
</classpath>
<formatter type="plain" usefile="false"/>
<test name="com.example.BusinessClassTest" />
</junit>
</target>
</project>
JUnit 输出
Testsuite: com.example.BusinessClassTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.889 sec
Testcase: returnString took 0.779 sec
Caused an ERROR
Could not modify all classes [class com.example.StaticDependency]
org.mockito.exceptions.base.MockitoException: Could not modify all classes [class com.example.StaticDependency]
at com.example.BusinessClassTest.returnString(Unknown Source)
Caused by: java.lang.IllegalStateException:
Byte Buddy could not instrument all classes within the mock's type hierarchy
This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:
- Compiled by older versions of scalac
- Classes that are part of the Android distribution
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:280)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClassStatic(InlineBytecodeGenerator.java:221)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClassStatic(TypeCachingBytecodeGenerator.java:63)
at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createStaticMock(InlineDelegateByteBuddyMockMaker.java:560)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createStaticMock(InlineByteBuddyMockMaker.java:83)
at org.mockito.internal.util.MockUtil.createStaticMock(MockUtil.java:147)
at org.mockito.internal.MockitoCore.mockStatic(MockitoCore.java:142)
at org.mockito.Mockito.mockStatic(Mockito.java:2164)
at org.mockito.Mockito.mockStatic(Mockito.java:2101)
Caused by: java.lang.AbstractMethodError: org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper.wrap(Lnet/bytebuddy/description/type/TypeDescription;Lorg/objectweb/asm/ClassVisitor;Lnet/bytebuddy/implementation/Implementation$Context;Lnet/bytebuddy/pool/TypePool;Lnet/bytebuddy/description/field/FieldList;Lnet/bytebuddy/description/method/MethodList;II)Lorg/objectweb/asm/ClassVisitor;
at net.bytebuddy.asm.AsmVisitorWrapper$Compound.wrap(AsmVisitorWrapper.java:746)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor.visit(TypeWriter.java:4906)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:569)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:424)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3951)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2213)
at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:224)
at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:123)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3661)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.transform(InlineBytecodeGenerator.java:394)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:276)
事实证明我的问题是我的 ivy.xml
文件中的 byte-buddy-dep
依赖性适得其反。正如 @temp-droid pointed out in a GitHub issue 我在 Mockito 项目中提出的同样的问题,将其替换为 byte-buddy
和 byte-buddy-agent
会起作用。
然后我意识到 Ivy 不要求我明确列出子依赖项(我不知道它知道如何解决它们),所以我能够更新到更简单的 ivy.xml
,它在 Ant 和 IntelliJ 中同样有效:
<ivy-module version="2.0">
<info organisation="com.example" module="MockitoTest"/>
<dependencies>
<dependency org="junit" name="junit" rev="4.13.2"/>
<dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
</dependencies>
</ivy-module>