Go中的模拟随机生成器

For my tests purpose, I would like to mock random numbers in Go. So I created the Random interface. During unit testing I return the identity function, while for implementation I generate a random number with rand package.

It is the right way for mocking random numbers in Go? Any help appreciated.

Go Playground: https://play.golang.org/p/bibNnmY2t1g

main:

package main

import (
    "time"
    "math/rand"
    "fmt"
)

func init() {
    rand.Seed(time.Now().UnixNano())
}

type Random interface {
    Uint(_ uint) uint
}

type rndGenerator func(n uint) uint

type Randomizer struct {
    fn rndGenerator
}

func NewRandomizer(fn rndGenerator) *Randomizer {
    return &Randomizer{fn: fn}
}

func (r *Randomizer) Uint(n uint) uint {
    return r.fn(n)
}

func fakeRand(n uint) uint { return n }
func realRand(_ uint) uint { return uint(rand.Uint64()) }

func main() {
    fakeRnd := NewRandomizer(fakeRand).Uint
    fmt.Println(fakeRnd(1))
    fmt.Println(fakeRnd(2))

    realRnd := NewRandomizer(realRand).Uint
    fmt.Println(realRnd(0))
    fmt.Println(realRnd(0))
}

test:

package main

import (
    "testing"
    "math/rand"
    "reflect"
)

func TestNewRandomizer(t *testing.T) {
    fn := func(n uint) uint { return n }

    type args struct {
        fn rndGenerator
    }
    tests := []struct {
        name string
        args args
        want *Randomizer
    }{
        {
            "test",
            args{fn},
            &Randomizer{fn},
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := NewRandomizer(tt.args.fn); reflect.TypeOf(got) != reflect.TypeOf(tt.want) {
                t.Errorf("NewRandomizer() = %v, want %v", got, tt.want)
            }
        })
    }
}

func TestRandomer_Uint(t *testing.T) {
    rnd := uint(rand.Uint64())

    type fields struct {
        fn rndGenerator
    }
    type args struct {
        n uint
    }
    tests := []struct {
        name   string
        fields fields
        args   args
        want   uint
    }{
        {
            "test",
            fields{func(n uint) uint { return n }},
            args{rnd},
            rnd,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            r := &Randomizer{
                fn: tt.fields.fn,
            }
            if got := r.Uint(tt.args.n); got != tt.want {
                t.Errorf("Randomizer.Uint() = %v, want %v", got, tt.want)
            }
        })
    }
}

func Test_fakeRand(t *testing.T) {
    rnd := uint(rand.Uint64())

    type args struct {
        n uint
    }
    tests := []struct {
        name string
        args args
        want uint
    }{
        {
            "test",
            args{rnd},
            rnd,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := fakeRand(tt.args.n); got != tt.want {
                t.Errorf("fakeRand() = %v, want %v", got, tt.want)
            }
        })
    }
}

func Test_realRand(t *testing.T) {
    type args struct {
        in0 uint
    }
    tests := []struct {
        name string
        args args
        want bool
    }{
        {
            "test",
            args{0},
            true,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := realRand(tt.args.in0); got < 1 {
                t.Errorf("realRand() = %v, want %v", got, tt.want)
            }
        })
    }
}

Your example makes no real use of the Random interface since the mocking is being done at the function field level of the Randomizer type.

I would recommend, if possible, to ditch the function field as well as the functions and instead define two separate implementations of the Random interface. You can use empty structs for this, they may look weird at first but they have the nice property of taking up 0 bytes of memory.

The main reason for the recommendation is that when you use function fields you lose the ability to compare your struct type values with reflect.DeepEqual. This is because two function values are only equal if they have the same type and both are nil.

As an example let's first look at your TestNewRandomizer which is a symptom of the issue stated above. In the test you're just comparing the type of the return value which is something already ensured by the compiler, so the test is absolutely pointless.

Now, let's say you drop the test, since it's useless, but for some reason you keep the function field. Because of this any struct type that depends on *Randomizer will also be untestable with DeepEqual and you'll have the same difficulties when trying to come up with a test for that type.

package main

import (
    "time"
    "math/rand"
    "fmt"
)

func init() {
    rand.Seed(time.Now().UnixNano())
}

type Random interface {
    Uint(_ uint) uint
}

type Randomizer struct {}

func NewRandomizer() Randomizer {
    return Randomizer{}
}

func (r Randomizer) Uint(n uint) uint {
    return uint(rand.Uint64())
}

type FakeRandomizer struct {}

func NewFakeRandomizer() FakeRandomizer {
    return FakeRandomizer{}
}

func (r FakeRandomizer) Uint(n uint) uint {
    return n
}

func main() {
    fakeRnd := NewFakeRandomizer().Uint
    fmt.Println(fakeRnd(1))
    fmt.Println(fakeRnd(2))

    realRnd := NewRandomizer().Uint
    fmt.Println(realRnd(0))
    fmt.Println(realRnd(0))
}

Note that i'm intentionally returning values instead of pointers since empty structs are smaller than pointers.

I have a method which generates a random integer and returns true if the integer is less than or equal to 50 and false if the integer is greater than 50 in the range [0, 100).

This is how I have created my structures to mock the functionality:

type Decider struct {
    RandImpl func(int) int
}

func (d *Decider) Decide(randRange int) bool {
    randVal := d.RandImpl(randRange)
    log.Info("RandVal: ", randVal)
    if randVal <= 50 {
        return true
    }
    return false
}

I'm calling this method in this manner:

rand.Seed(time.Now().UnixNano())
decider := decide.Decider{
    RandImpl: func(x int) int { return rand.Intn(x) },
}
decider.Decide(100)

In my _test.go file, I have this:

decider := Decider{
    RandImpl: func(x int) int { return 42 },
}
ret := decider.Decide(100)
assert.True(t, ret)

This way you can mock the functionality of the random number generator.