概要
前回 go-workers のテスト戦略を考えてみました
更に良い方法が見つかったのでコードを紹介します
環境
- macOS 10.15.2
- golang 1.12.9
main.go
変更点は Worker
構造体の関数として Perform
を登録したのでそれを workers.Process
に渡しています
vim $GOPATH/src/github.com/hawksnowlog/a/worker/main/main.go
package main
import (
"github.com/jrallison/go-workers"
"github.com/hawksnowlog/a/worker"
)
func main() {
workers.Configure(map[string]string{
"server": worker.GetRedisURL(),
"process": "1",
})
// ここにワーカーのメイン処理を指定します
w := worker.NewWorker()
// w が持つ Perform 関数をワーカーのメイン関数として設定します
workers.Process("default", w.Perform, 1)
workers.Run()
}
worker.go
先程紹介したように Worker
構造体の関数として Perform
を定義しています
Perform
内で NewWorker
を呼ばなくなり直接 Worker
構造体のフィールドにアクセスできるようになっています
こうすることで Worker
構造体のフィールドをモックにすることで Perform
を直接呼び出してテストできるようにしています
vim $GOPATH/src/github.com/hawksnowlog/a/worker/worker.go
package worker
import (
"fmt"
"github.com/jrallison/go-workers"
)
// Worker 構造体は外部リソースを管理する構造体を持っています
// テスト時にモックしたいリソースは Interface として定義します (重要)
type Worker struct {
logger *Logger
Redis RedisInterface
}
// Worker 構造体を生成します
func NewWorker() *Worker {
return &Worker{
logger: NewLogger(),
Redis: NewRedis(),
}
}
// ワーカーのメイン処理になります
// ここで外部リソースにアクセスしたりします
// Worker 構造体の関数として定義することでテストしやすいコードにしています
func (w *Worker) Perform(msg *workers.Msg) {
err := w.Redis.Open()
if err != nil {
w.logger.Info(err.Error())
return
}
defer w.Redis.Close()
a, _ := msg.Args().Array()
w.logger.Info(fmt.Sprintf("%s", a[0]))
err = w.Redis.Set(msg.Jid())
if err != nil {
w.logger.Info(err.Error())
return
}
data, err := w.Redis.Get()
if err != nil {
w.logger.Info(err.Error())
return
}
w.logger.Info(string(data))
}
redis.go
Perform
の終了後に defer
を使って Redis とのコネクションを Close
しているので冒頭で Open
できるように関数を追加しています
vim $GOPATH/src/github.com/hawksnowlog/a/worker/redis.go
package worker
import (
"github.com/gomodule/redigo/redis"
"os"
)
// モックするために Interface を定義します
type RedisInterface interface {
Set(data string) error
Get() ([]byte, error)
Close()
Open() error
}
type Redis struct {
client redis.Conn
}
const keyName = "data"
const defaultRedisURL = "localhost:6379"
func NewRedis() *Redis {
client, _ := redis.Dial("tcp", GetRedisURL())
return &Redis{
client: client,
}
}
func GetRedisURL() string {
if url, ok := os.LookupEnv("REDIS_URL"); ok {
return url
}
return defaultRedisURL
}
func (r *Redis) Open() error {
client, err := redis.Dial("tcp", GetRedisURL())
if err != nil {
return err
}
r.client = client
return nil
}
func (r *Redis) Set(data string) error {
_, err := r.client.Do("set", keyName, data)
if err != nil {
return err
}
return nil
}
func (r *Redis) Get() ([]byte, error) {
data, err := redis.Bytes(r.client.Do("get", keyName))
if err != nil {
return nil, err
}
return data, nil
}
func (r *Redis) Close() {
r.client.Close()
}
fake_redis.go
redis.go 同様に Open
を追加しています
vim $GOPATH/src/github.com/hawksnowlog/a/worker/fake_redis.go
package worker
type FakeRedis struct {
}
const FakeJid = "054f5c637bc26dbd7491a758"
func NewFakeRedis() *FakeRedis {
return &FakeRedis{}
}
func (r *FakeRedis) Open() error {
return nil
}
func (r *FakeRedis) Set(data string) error {
return nil
}
func (r *FakeRedis) Get() ([]byte, error) {
return []byte(FakeJid), nil
}
func (r *FakeRedis) Close() {
}
worker_test.go
そしてテストを変更します
NewWorker
で Worker
を作成したらそのまま Perform
を呼び出しています
その前に FakeRedis
のモックを Worker
フィールドに設定します
こうすることで Redis に直接アクセスすることなく Perform
関数内でモックを使ってくれます
vim $GOPATH/src/github.com/hawksnowlog/a/worker/worker_test.go
package worker
import (
"fmt"
"github.com/jrallison/go-workers"
"testing"
)
func TestWorker_Perform(t *testing.T) {
// キューイングされたメッセージを擬似的に生成
msg, _ := workers.NewMsg(fmt.Sprintf(`{"jid":"%s","args":["1","2"]}`, FakeJid))
mainWorker := NewWorker()
// Redis には実際にアクセスしない FakeRedis を設定
mainWorker.Redis = NewFakeRedis()
mainWorker.Perform(msg)
}
最後に
おそらくこの方法が一番良い方法だと思います
テストのなので良い悪いとかはないと思うのですがソースコードもキレイでかつテストも書きやすいコードになるのでは思います
参考までに
0 件のコメント:
コメントを投稿