如何为包含随机数的方法编写JUnit测试?
解决依赖随机数的JUnit测试问题
我懂你遇到的困扰啦——依赖随机数的函数确实不好写稳定的测试,每次跑测试结果都可能跳变,根本没法精准断言预期值。核心思路是把随机数的生成逻辑抽成可控制的依赖,让测试时能完全掌控输入,从而验证函数的逻辑正确性。下面给你具体方案和示例:
第一步:改造原代码,解耦随机数依赖
首先得把硬编码的new Random()抽出来,通过构造方法或者 setter 注入,这样测试时可以传入我们自己控制的随机数生成器。比如原来的代码可能是这样的:
public class RpsGame { // 原方法:直接在内部创建Random,测试时无法控制 public String playRandomChoice() { Random random = new Random(); int choiceNum = random.nextInt(3); return getChoiceByNum(choiceNum); } // 根据数字返回对应的手势 public String getChoiceByNum(int num) { switch(num) { case 0: return "rock"; case 1: return "paper"; case 2: return "scissor"; default: throw new IllegalArgumentException("无效数字:" + num); } } }
我们把它改造成支持依赖注入的版本:
public class RpsGame { private final Random random; // 构造方法注入Random,生产环境用new Random(),测试用可控的实例 public RpsGame(Random random) { this.random = random; } public String playRandomChoice() { int choiceNum = random.nextInt(3); return getChoiceByNum(choiceNum); } // 保留原有的getChoiceByNum方法 public String getChoiceByNum(int num) { // ... 实现同上 } }
第二步:编写稳定的JUnit测试
现在我们有两种方式来写测试:
方式1:使用固定种子的Random
选一个特定的种子,确保nextInt(3)返回我们想要的数字,这样就能精准断言结果:
import org.junit.Test; import static org.junit.Assert.assertEquals; public class RpsGameTest { @Test public void playRandomChoice_returnsRock_whenRandomGives0() { // 用固定种子的Random,确保第一次nextInt(3)返回0(你可以提前验证种子对应的输出) Random fixedRandom = new Random(456); RpsGame game = new RpsGame(fixedRandom); assertEquals("rock", game.playRandomChoice()); } @Test public void playRandomChoice_returnsPaper_whenRandomGives1() { Random fixedRandom = new Random(789); // 这个种子要保证nextInt(3)返回1 RpsGame game = new RpsGame(fixedRandom); assertEquals("paper", game.playRandomChoice()); } }
方式2:用Mockito Mock Random(更灵活)
如果不想纠结种子,直接用Mockito框架来指定nextInt(3)的返回值,完全控制输入:
import org.junit.Test; import org.mockito.Mockito; import static org.junit.Assert.assertEquals; public class RpsGameTest { @Test public void playRandomChoice_returnsScissor_whenRandomGives2() { // Mock一个Random实例 Random mockRandom = Mockito.mock(Random.class); // 指定调用nextInt(3)时返回2 Mockito.when(mockRandom.nextInt(3)).thenReturn(2); RpsGame game = new RpsGame(mockRandom); assertEquals("scissor", game.playRandomChoice()); } }
为什么要这么做?
我们测试的核心是验证函数的逻辑是否正确——也就是当随机数返回0/1/2时,是否能正确调用getChoiceByNum并返回对应的手势。通过解耦随机数依赖,我们把“随机”的部分变成了可控制的输入,这样测试就能稳定通过,不会因为随机波动失败。
如果是没法修改的遗留代码,你可以用PowerMock来mock静态的new Random()调用,但这种方式不推荐,最好还是优先改造代码解耦依赖,让代码更易测。
内容的提问来源于stack exchange,提问作者OrichaOki
相关产品推荐
相关产品推荐

