用Random编写单元测试

时间:2019-05-21 09:06:55

标签: java unit-testing junit

我正在尝试测试一种方法的行为以生成带有一些参数的URL。该方法利用了辅助方法,而辅助方法又使用了new Random().nextDouble()

private static String generateChecksum() {
    return "s" + new Double(Math.floor(Instant.now().getEpochSecond() / 10800000)).longValue() % 10 +
            new Double(Math.floor(10000000000000L * new Random().nextDouble())).longValue();
}

我对此特定测试的目标是确保方法的输出与我期望的相符。我的问题是我无法预测Random.nextDouble()的结果。

我读到您可以模拟结果,并且我认为覆盖该方法的每个实例。我发现自己不能I shouldn't做那件事,但是我应该重新编写代码以适合我的测试。

如何重写它?我是否将校验和与参数一起传递到预先生成的方法中,所以我的流程来自:

1. Main()
2. generateUrl(params)
2a. generateChecksum() // for use in generateUrl
3. return URL

1. Main()
2. generateChecksum()
3. generateUrl(params, checksum)
4. return URL

还是我错过了什么?谢谢!

4 个答案:

答案 0 :(得分:1)

为了使此类代码可测试,您有两件事很重要:

  • 您想以某种方式伪造系统时钟。此article告诉您如何使用Clock类在Java 8中做到这一点
  • 随机出现时,只需使用 seed 。这使您可以“预测”生产代码中将出现的随机数。

换句话说:您可以控制时间生产代码将看到的随机数!

或者,将整个计算简单地委托到可以轻松模拟的东西中,例如:

public class CheckSumGenerator {

  public String generate(...) { ...

现在,您可以轻松模拟该类,并使其返回您想要返回的任何内容。不用考虑时钟或随机数。

但是请记住:设置种子仍然是一个好主意,理想情况下,您可能希望使用 secure 随机数作为种子。

答案 1 :(得分:1)

最简单的解决方案是在您的方法中添加一个包含该随机数的新参数:

private static String generateChecksum(double r) {
    return "s" + new Double(Math.floor(Instant.now().getEpochSecond() / 10800000)).longValue() % 10 +
            new Double(Math.floor(10000000000000L * r)).longValue();
}

static String generateUrl(double r) {
    // call generateChecksum(r)
}

public static String generateUrl() {
    // trivial
    return generateUrl(new Random().nextDouble());
}

现在,您可以轻松测试generateUrl(double r)方法。您不需要测试generateUrl()方法,因为它很简单。

答案 2 :(得分:0)

如果无法提前知道结果,则无法进行测试。

您需要注入随机性源作为依存关系,以便可以在测试中提供常量。

public class MyClass {
    private final Supplier<Double> randomNumberGenerator;

    public MyClass(Supplier<Double> randomNumberGenerator) {
        this.randomNumberGenerator = randomNumberGenerator;
    }

    private String generateChecksum() {
        //... randomNumberGenerator.get();
    }
}

在您的应用程序代码中,像这样创建对象:

new MyClass(() -> new Random().nextDouble());

在您的测试中,如下所示创建它:

new MyClass(() -> 10.0);

您可以使用时间戳记执行相同的操作。 Supplier<Instant>Instant::now() -> Instant.ofEpochSecond(123456)

答案 3 :(得分:0)

虽然您确实不知道使用随机组件的方法的 exact 输出是正确的,但我认为没有必要测试任何精确的输出,而是测试正确的输出输出的格式,即验证输出是 a 有效URL,而不是消除随机性的特定实例。

当然,输出可能会偶然通过此检查,但是选择特定的种子实际上会遇到相同的问题。