构造函数启动线程的类中私有变量赋值及JUnit测试方案咨询
解决JUnit测试中MainClass线程启动与字段赋值的冲突问题
这个问题的核心矛盾在于构造函数中直接启动线程,导致对象创建后线程立即开始执行,而我们还没来得及给que字段赋值。下面提供几个从优雅到妥协的解决方案,你可以根据实际场景选择:
方案1:重构代码,让线程启动可控(推荐)
最根本的解决办法是修改MainClass的设计,将线程启动的逻辑从构造函数中剥离,让调用者(包括测试代码)控制线程启动时机。这样既符合单一职责原则,也让测试变得简单。
修改后的MainClass:
public class MainClass extends Thread { private SomeQue que; private static final String THREAD_NAME = "s_thread"; private volatile boolean isRunning = false; // 加volatile保证线程可见性 // 基础构造函数:只初始化线程名称,不启动线程 public MainClass() { setName(THREAD_NAME); } // 生产环境构造函数:传入que并自动启动线程 public MainClass(SomeQue que) { this(); this.que = que; startThread(); } // 暴露给测试的手动启动方法 public void startThread() { if (!isRunning) { isRunning = true; start(); } } @Override public void run() { while (isRunning) { if (que != null) { Object obj = que.pop(); if (obj != null) { // 你的业务逻辑 } } // 加个短暂休眠避免空轮询浪费资源 try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); isRunning = false; } } } // 必要的getter/setter public void setQue(SomeQue que) { this.que = que; } public boolean isRunning() { return isRunning; } }
对应的JUnit测试代码:
@Test public void testMainClassLogic() { // 1. 创建测试用的队列并填充数据 SomeQue testQue = new SomeQue(); testQue.push("test-data"); // 2. 创建MainClass实例(此时线程未启动) MainClass main = new MainClass(); // 3. 设置que字段(甚至可以直接用setQue方法,不用反射) main.setQue(testQue); // 4. 手动启动线程 main.startThread(); // 5. 执行测试验证逻辑,比如验证队列数据被处理 // ... 你的断言代码 // 测试结束后停止线程 main.interrupt(); }
方案2:使用PowerMock拦截构造函数(适合无法修改源码的场景)
如果因为某些原因不能修改MainClass的源码,可以用PowerMock来拦截构造函数中的线程启动逻辑,先给que赋值再让线程正常运行。
测试代码示例:
@RunWith(PowerMockRunner.class) @PrepareForTest(MainClass.class) // 告诉PowerMock要处理这个类 public class MainClassTest { @Test public void testRunWithQue() throws Exception { SomeQue testQue = new SomeQue(); testQue.push("test-item"); // 1. 创建MainClass的部分模拟对象,mock掉start()方法,避免构造时启动线程 MainClass main = PowerMock.createPartialMock(MainClass.class, "start"); // 2. 用反射设置que和isRunning字段 ReflectionTestUtils.setField(main, "que", testQue); ReflectionTestUtils.setField(main, "isRunning", true); // 3. 现在可以手动调用run方法测试业务逻辑,或者解除mock后启动线程 main.run(); // 4. 验证你的业务逻辑是否正确执行 // ... 断言代码 } }
方案3:临时妥协:在run方法中加入等待逻辑(不推荐)
如果既不能修改源码也不想用PowerMock,可以临时在run方法的空que分支中加入短暂休眠,给测试代码留出赋值的时间。但这个方案不稳定,依赖线程调度,只适合临时测试。
修改run方法:
@Override public void run() { while (isRunning()) { if (que == null) { try { Thread.sleep(100); // 等待100ms,给测试时间赋值 } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } continue; } Object obj = que.pop(); if (obj != null) { // 业务逻辑 } } }
测试代码:
@Test public void testMainClass() throws InterruptedException { SomeQue testQue = new SomeQue(); testQue.push("test-data"); // 创建对象,线程启动后会进入等待que的循环 MainClass main = new MainClass(); // 等待足够长的时间让线程进入休眠 Thread.sleep(200); // 用反射设置que ReflectionTestUtils.setField(main, "que", testQue); // 验证逻辑 // ... 断言代码 // 停止线程 ReflectionTestUtils.setField(main, "isRunning", false); }
内容的提问来源于stack exchange,提问作者mae
相关产品推荐
相关产品推荐

