How do I test an func which depends on (pseudo)-random numbers, e.g. rand.Intn()?

4,130 views
Skip to first unread message

Doug Ireton

unread,
Mar 14, 2017, 11:45:22 PM3/14/17
to golang-nuts
I'm a new Gopher and I'm working through "Learn Go" by Nathan Youngman and trying to TDD the exercises to learn how to write testable Go code.

I have a function to return a random spaceline from a string array.

In Go, how do I test functions which depend on random numbers? And, yes, I know that "math/rand" isn't truly random.

Is it as simple as setting a seed right before I run my test, e.g. rand.Seed(1)? Do I set rand.Seed(1) at the top of the _test.go file, or at the beginning of each unit test?

Also, am I seeding math.rand correctly in the Init() function? Will seeding it in the Init() function override any seeding I do in my tests?

My only other thought is to create an interface somehow to mock rand.Intn(), but this seems like overkill and I don't know enough about interfaces to know if this is inadvisable.

Miki Tebeka

unread,
Mar 15, 2017, 12:24:32 AM3/15/17
to golang-nuts
IMO the right approach is as you suggested - seed once in init.

init is called before your tests, so the seed you do in your test will override it.

Dan Kortschak

unread,
Mar 15, 2017, 12:26:00 AM3/15/17
to Doug Ireton, golang-nuts
If you let randSpaceline take a *rand.Rand then you can control the
source of the randomness and arbitrarily set the seed. You also get the
advantage of having a non-mutex protected rand source if you don't need
it (we do a similar thing and used rand.<Func> if the *rand.Rand is nil
as a convenience).

On Tue, 2017-03-14 at 18:50 -0700, Doug Ireton wrote:
> I'm a new Gopher and I'm working through "Learn Go" by Nathan
> Youngman and 
> trying to TDD the exercises to learn how to write testable Go code.
>
> I have a function to return a random spaceline 
> <https://play.golang.org/p/g5JnrIFyjo> from a string array.

Egon

unread,
Mar 15, 2017, 3:07:02 AM3/15/17
to golang-nuts
Write tests for the intended behavior. It seems you are over-specifying the test. If something needs to return something random, then test whether the result is random enough.

+ Egon 

xiio...@gmail.com

unread,
Mar 15, 2017, 8:20:17 AM3/15/17
to golang-nuts


"Seed uses the provided seed value to initialize the default Source to a deterministic state. If Seed is not called, the generator behaves as if seeded by Seed(1). " 

So even without calling rand.Seed your output will be the same everytime you run the program unless you take action to make that different. 

( note that the second value obtained from Rand.IntN is not the same as that for the first value obtained from Rand.IntN seeded with the value 2 )

xiio...@gmail.com

unread,
Mar 15, 2017, 8:37:16 AM3/15/17
to golang-nuts

Also, am I seeding math.rand correctly in the Init() function? Will seeding it in the Init() function override any seeding I do in my tests?


You're using time.Now().UTC().UnixNano()) but

 time.Now().UnixNano()

or 

time.Now().Unix()

seem just as good

Note UnixNano() may be beyond the accuracy at with the CPU reports - so it may well trail zeros .. conversely 1 sec intervals in Unix() may be too large ..

time.Now().UnixNano()/1000 ie microseconds

or similar could be a good compromise


I assume the reason you are doing this is to get a different set of randoms everytime you run - obviously that will make testing next to impossible - my suggestion based on experience - test with a set of seeds - ie 1,2,3 in rand.Seed  .. testing with just one seed often gave me corner case results which misled me on the validity of my code - testing with a set of seeds should ensure that your random numbers are "randomly selected"

mhh...@gmail.com

unread,
Mar 15, 2017, 8:59:05 AM3/15/17
to golang-nuts
Hi,

if you wish not declare a struct and its interface, you might simply make a function type.

See
https://play.golang.org/p/xuycrPc8sF

Then in your main program, you consume the func type, and inject a func impl.

It is not as versatile as interface, but that can make the job.

Robert Johnstone

unread,
Mar 15, 2017, 9:56:21 AM3/15/17
to golang-nuts
If you are fixing the seed, you are probably over constraining your test.  Even though you are calling rand, there must be some post-conditions that the function is supposed to guarantee.  In this case, you probably expect all options to be returned with equal probability, so you should call the function many times, and calculate those probabilities.  There is a whole field for statistical testing.

prade...@gmail.com

unread,
Mar 16, 2017, 2:25:51 AM3/16/17
to golang-nuts
You would test it the same way you test anything that depends on IO, you don't make your function depend on a random number or an unpredictable value, the random number should be a function parameter. 

In order to test something it must be made testable. Right now your function is not.

Michael Jones

unread,
Mar 16, 2017, 4:00:29 AM3/16/17
to prade...@gmail.com, golang-nuts
You could also capture a (p)random stream as a test data vector and use it in lieu of the PRNG during tests. This is the same as seeding iff and only iff the PRNG is never changed, Should that change, then so will the tests unless you capture a test vector now. (For example, if you have a timing benchmark and the amount of work varies, then having a a cache of static random values makes the timing always be comparable.)

This is pedantic and probably not important to most people. But if it matters to you, it is the answer.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Michael T. Jones
michae...@gmail.com

Simon Ritchie

unread,
Mar 16, 2017, 9:18:37 AM3/16/17
to golang-nuts
As some posters have already said, it depends what you are trying to test.

If you want to test what your solution does when it receives a randomly-generated number, then you don't need to use random numbers. You can hide the generation of the random number in a type which is defined by an interface. Then you can supply a dummy version of the type which supplies numbers that you choose specifically for your test. So for example, if you want to test what your solution does when it receives a randomly-generated number less than 10, you set up a test that supplies it with 8.

On the other hand, if you want to test the random number generator, for example to test its randomness, then this approach is no good.

Simon

Reply all
Reply to author
Forward
0 new messages