JMH中如何实现JUnit@BeforeAll/@BeforeClass的等效功能?
JMH实现全局仅执行一次Setup的方案
解决方案1:使用静态代码块
静态代码块在类加载阶段仅执行一次,完全符合你想要的「类似JUnit @BeforeAll」的效果,无需依赖JMH的Setup级别:
package de.shd.kps.backend.customer.benchmark; import java.util.concurrent.TimeUnit; import org.junit.runner.RunWith; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.springframework.test.context.junit4.SpringRunner; @State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @RunWith(SpringRunner.class) public class ExampleTest extends AbstractBenchmarkHarnessForTests { private static int setupCalls = 0; // 静态代码块:类加载时仅执行一次 static { setupCalls++; System.err.println("Setup-Calls:"); System.err.println(setupCalls); } @Benchmark public void benchmark() { } @Benchmark public void benchmark2() { } @Benchmark public void benchmark3() { } }
解决方案2:使用Scope.Global的独立State类
如果你需要依赖JMH的生命周期管理(比如初始化逻辑需要在JMH启动后执行),可以将初始化逻辑放到一个Scope.Global的State类中,这个State会在整个基准测试生命周期中仅初始化一次:
package de.shd.kps.backend.customer.benchmark; import java.util.concurrent.TimeUnit; import org.junit.runner.RunWith; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.springframework.test.context.junit4.SpringRunner; @State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @RunWith(SpringRunner.class) public class ExampleTest extends AbstractBenchmarkHarnessForTests { // 全局唯一的State类 @State(Scope.Global) public static class GlobalInitState { int setupCalls = 0; @Setup(Level.Trial) public void setup() { setupCalls++; System.err.println("Setup-Calls:"); System.err.println(setupCalls); } } // Benchmark方法注入全局State @Benchmark public void benchmark(GlobalInitState globalState) { // 可使用globalState中的初始化资源 } @Benchmark public void benchmark2(GlobalInitState globalState) { } @Benchmark public void benchmark3(GlobalInitState globalState) { } }
为什么原来的配置会触发3次Setup
你遇到的问题核心原因是:JMH会将同一个类中的每个@Benchmark方法视为独立的测试任务,每个任务都会启动自己的Trial流程。即使使用Scope.Benchmark的State,每个Benchmark任务的Trial都会触发一次@Setup(Level.Trial),因此3个Benchmark方法会导致3次Setup调用。
而Scope.Global的State是全局共享的,所有Benchmark方法都会复用同一个实例,因此只会执行一次Trial级别的Setup;静态代码块则完全脱离JMH的生命周期,类加载时仅执行一次。
内容的提问来源于stack exchange,提问作者Hag2bard
相关产品推荐
相关产品推荐

