简体   繁体   中英

Writing unit tests with Random

I'm trying to test the behaviour of a method to generate a URL with a few parameters. The method makes use of a helper method which in turn makes use of 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();
}

My goal for this particular test is to ensure the output of the method matches what I expect. My issue is I can't predict the result of Random.nextDouble() .

I read you can mock the results and I thought that overriding every instance of that method. I found out I couldn't do that but also that I shouldn't , but instead I should re-write my code to fit my test.

How do I rewrite it? Do I pass the checksum into the method pre-generated along with the parameters so my flow goes from:

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

to

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

Or am I missing something? Thanks!

In order to make such code testable, you there are two things that matter:

  • you want to somehow fake the system clock. This article tells you how you can do that in Java 8 by using the Clock class
  • when random comes in, simply use a seed . Which allows you to "predict" the random numbers that will come up in your production code.

In other words: you can control the time and the random number that your production code will see!

Alternatively, simple delegate the whole computation into something that you can easily mock, such as:

public class CheckSumGenerator {

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

Now, you can easily mock that class, and have it return whatever you want it to return. Without thinking about clocks or random numbers.

But keep in mind: setting a seed might still be a good idea, and ideally, you might want to use a secure random number as seed.

The simplest solution is to add a new parameter holding that random number to your methods:

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());
}

Now you can just test generateUrl(double r) method easily. You don't need to test generateUrl() method because it is trivial.

You can't test something when the results are not known ahead of time.

You need to inject the source of randomness as a dependency so that you can supply constants in your tests.

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

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

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

In your application code, create the object like this:

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

In your test, create it like this, for example:

new MyClass(() -> 10.0);

You can do the same thing with the timestamp. Supplier<Instant> , Instant::now , () -> Instant.ofEpochSecond(123456)

While it is true that you do not know the exact output of the method using a random component, I don't think it is necessary to test for any exact output, but rather for the correct format of the output, ie, validate that the output is a valid URL instead of a specific instance where you eliminate the randomness.

Of course the output might pass this check by chance, but by picking a specific seed you essentially run into the same problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM