自作した社員抽選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確率偏ってるんじゃない?」と聞かれた時はこの結果を見せて正しいロジックであることを証明したいと思います