4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ランダムに抽選する関数の確率テスト(Golang)

Posted at

自作した社員抽選Slack Botの抽選確率がランダムになっているか調査するテストを書いてみました

Go + AWS LambdaでSlackの社員抽選botを作った話
https://blog.acall.jp/2019/11/develop-slack-lottery-bot/

テスト対象関数

main.go
package main

import (
	"math/rand"
	"time"
)


func lotteryOneUserFromUsers(userIDs []string) string {
	rand.Seed(time.Now().UnixNano())
	userID := userIDs[rand.Intn(len(userIDs))]
	return userID
}

スライスを渡すと要素の1つがランダムで返ってきます
とてもシンプルな関数です

テストコード

main_test.go
package main

import (
	"fmt"
	"math"
	"testing"
)

func Test_lotteryOneUserFromUsers(t *testing.T) {
	// initialize
	userIDs := []string{"a", "b", "c", "d", "e"}
	counts := make(map[string]int)
	for _, id := range userIDs {
		counts[id] = 0
	}

	// sampling
	const loopCnt int = 1000
	var i int
	var userID string
	for i < loopCnt {
		userID = lotteryOneUserFromUsers(userIDs)
		counts[userID]++
		i++
	}

	// calculation
	const allowErrRate = 0.05
	var probability float64
	var errRate float64
	expectProbability := float64(1) / float64(len(userIDs))

	fmt.Printf("ID: count, percentage\n")
	for k, v := range counts {
		probability = float64(v) / float64(loopCnt)
		errRate = math.Abs(expectProbability - probability)
		fmt.Printf("%s: %d ", k, v)
		fmt.Printf("%v per \n", probability*100.0)
		if errRate > allowErrRate {
			t.Errorf("Error Rate is too big. Label: %s, Error Rate: %f", k, errRate)
		}
	}
}

初期化

userIDs := []string{"a", "b", "c", "d", "e"}
counts := make(map[string]int)
for _, id := range userIDs {
	counts[id] = 0
}

userIDsに適当な引数定義しています
このuserIDsの抽選を何度も繰り返し、確率を求めます

countsは当選回数を格納するmapです

サンプリング

const loopCnt int = 1000
var i int
var userID string
for i < loopCnt {
	userID = lotteryOneUserFromUsers(userIDs)
	counts[userID]++
	i++
}

loopCntにループ回数を定義し、当選回数をcounts追加するループを回しています

確率の計算

const allowErrRate = 0.05
var probability float64
var errRate float64
expectProbability := float64(1) / float64(len(userIDs))

fmt.Printf("ID: count, percentage\n")
for k, v := range counts {
	probability = float64(v) / float64(loopCnt)
	errRate = math.Abs(expectProbability - probability)
	fmt.Printf("%s: %d ", k, v)
	fmt.Printf("%v per \n", probability*100.0)
	if errRate > allowErrRate {
		t.Errorf("Error Rate is too big. Label: %s, Error Rate: %f", k, errRate)
	}
}
変数 説明
probability 計算した確率
expectProbability 理想確率
errRate 計算した確率誤差
allowErrRate 許容確率誤差

確率の理想値と実際の確率の差を求めて許容確率誤差を超えていないか確認するだけです

実行

go test . -v でテストを実行してみます
-vはテストPASS時にも標準出力を表示するオプションです

$ go test . -v
=== RUN   Test_lotteryOneUserFromUsers
ID: count, percentage
a: 202 20.200000000000003 per
b: 205 20.5 per
c: 209 20.9 per
d: 212 21.2 per
e: 172 17.2 per
--- PASS: Test_lotteryOneUserFromUsers (0.01s)
PASS
ok  	lottery	0.021s

PASSしてますね
許容確率誤差をもっと小さくしてみると失敗するようになります

許容誤差0.01の場合
$ go test . -v
=== RUN   Test_lotteryOneUserFromUsers
ID: count
c: 193 19.3 per
d: 219 21.9 per
e: 190 19 per
a: 182 18.2 per
b: 216 21.6 per
--- FAIL: Test_lotteryOneUserFromUsers (0.01s)
    handler_test.go:41: Error Rate is big. Label: d, Error Rate: 0.019000
    handler_test.go:41: Error Rate is big. Label: a, Error Rate: 0.018000
    handler_test.go:41: Error Rate is big. Label: b, Error Rate: 0.016000
FAIL
FAIL	lottery	0.021s
FAIL

まとめ

「このbot確率偏ってるんじゃない?」と聞かれた時はこの結果を見せて正しいロジックであることを証明したいと思います

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?