在Go中模拟随机生成器

时间:2018-01-20 19:29:06

标签: unit-testing go random

为了我的测试目的,我想在Go中模拟随机数字。所以我创建了Random接口。在单元测试期间,我返回标识函数,而对于实现,我使用rand包生成一个随机数。

这是在Go中模拟随机数的正确方法吗?任何帮助表示赞赏。

去游乐场:https://play.golang.org/p/bibNnmY2t1g

主:

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

试验:

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

2 个答案:

答案 0 :(得分:0)

您的示例没有真正使用Random接口,因为模拟是在Randomizer类型的函数字段级别完成的。

如果可能的话,我建议抛弃函数字段和函数,而是定义Random接口的两个独立实现。你可以使用空结构,它们起初可能看起来很奇怪,但它们具有占用0字节内存的良好属性。

建议的主要原因是当您使用函数字段时,您将无法将结构类型值与reflect.DeepEqual进行比较。这是因为如果两个函数值具有相同类型且两者都是nil ,则它们只相等。

作为一个例子,让我们首先看看TestNewRandomizer,这是上述问题的症状。在测试中,您只是比较返回值的类型,这是编译器已经确保的,因此测试绝对没有意义。

现在,让我们说你放弃测试,因为它没用,但由于某种原因你保留了功能字段。因此,任何依赖于*Randomizer的结构类型对于DeepEqual也是不可测试的,并且在尝试为该类型进行测试时会遇到同样的困难。

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

请注意,我故意返回值而不是指针,因为空结构比指针小。

答案 1 :(得分:0)

我有一个生成随机整数的方法,如果该整数小于或等于50,则返回true;如果该整数大于[50,100],则返回false

这是我创建结构以模拟功能的方式:

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
}

我以这种方式调用此方法:

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

在我的_test.go文件中,我有这个:

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

通过这种方式,您可以模拟随机数生成器的功能。