Golangで単体テストコードの実装方法
解決済
回答 3
投稿
- 評価
- クリップ 1
- VIEW 2,481
前提・実現したいこと
Golangである基本的なメソッドの単体テストを実装しようとしています。
発生している問題・エラーメッセージ
しかし、データ型をどのように解決すればよいのかがわかりません。
下に示したのはAppのクラスの中で、Appのフィールドの一つにDaoを入れています。そして、Appのメソッド(A)の中ででDaoのメソッド(D)を呼び出しています。そのAppのメソッド(A)の単体テストがしたいです。しかし、そのAppのメソッド(A)の中で呼び出しているDaoのメソッド(D)のテストは不要ですので、そのDaoのメソッド(D)をtestではダミーのメソッドにしたいです。
該当のソースコード
package main
import (
"fmt"
)
// Dao
type Dao struct{}
func (this *Dao) D() {
fmt.Println("D")
}
// App
type App struct {
dao *Dao
}
func (this *App) SetDao(dao *Dao) {
this.dao = dao
}
func (this *App) A() {
this.dao.D()
}
func main() {
app := &App{dao: &Dao{}}
app.A() // D が返ってくるが、別の戻り値にしたい
}
試したこと
インターフェイスでの実装しました。ですが、Daoのメソッドはたくさんあるので、一つの単体テストのたびに、Daoのmethod全てを実装したDummyクラスを作る必要が生まれてしまうのでダメでした。
package main
import (
"fmt"
)
// Dao
type Dao struct{}
func (this *Dao) D() {
fmt.Println("D")
}
// App
type App struct {
// dao *Dao
dao Der
}
func (this *App) SetDao(dao *Dao) {
this.dao = dao
}
func (this *App) A() {
this.dao.D()
}
// D&Dummy Interface
type Der interface {
D()
}
// dummy
type Dummy struct{}
func (this *Dummy) D() {
fmt.Println("Dummy")
}
func main() {
app := &App{dao: &Dao{}}
app.A() // D
app2 := &App{dao: &Dummy{}}
app2.A() // Dummy
}
本当はJavaScriptの用に、あるクラスのメソッドをインスタン後に無名関数などでreplaceできれば簡単にdummyメソッドを追加できるのですが、Golangでは何かいい方法はないでしょうか?
補足情報(言語/FW/ツール等のバージョンなど)
golang 1.6
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
+1
Dao を interface にするのは同じですが、基本の BaseDao 型を作っておいて、テストで任意のメソッドをすげ替えたい場合は BaseDao を埋め込んだダミー型を宣言して、すげ替えたいメソッドだけ実装し、その他のメソッドは BaseDao に委譲するというのはどうでしょうか?
以下の実装例では説明とテストの都合のため、メソッドを増やしたり返り値もたせたりしています。
app.go
package app
//DAO
type Dao interface {
D() string
D2() string
}
type BaseDao struct {}
func (b *BaseDao) D() string {
return "D"
}
func (b *BaseDao) D2() string {
return "D2"
}
//APP
type App struct {
dao Dao
}
func (a *App) SetDao(d Dao) {
a.dao = d
}
func (a *App) A() string {
return a.dao.D()
}
func (a *App) A2() string {
return a.dao.D2()
}
app_test.go
package app
import "testing"
//TEST BaseDao
func TestApp_A(t *testing.T) {
app := &App{}
dao := &BaseDao{}
app.SetDao(dao)
if app.A() != "D" {
t.Error("invalid dao")
}
if app.A2() != "D2" {
t.Error("invalid dao")
}
}
//TEST Dummy
type DummyDao struct {
BaseDao
}
func (d *DummyDao) D() string {
return "Dummy"
}
func TestApp_A2(t *testing.T) {
app := &App{}
dao := &DummyDao{}
app.SetDao(dao)
if dao.D() != "Dummy" {
t.Error("invalid dao")
}
if dao.D2() != "D2" {
t.Error("invalid dao")
}
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
check解決した方法
0
nirasanさんとほとんど同じなのですが、以下の用にしました。
インターフェイス(Boarder)を定義しておき、それをテストのmock用の構造体に埋め込みます。
インターフェイスなのでDaoに必要な仮想メソッドが定義されており、AppのDaoの型チェックは通ります。
そして、テストで実際に必要なメソッドのみ実際にmockのメソッドとして実装します。
基底クラス(構造体)の埋め込みは不要なフィールドがついて来そうなのでやめました。
type mock1 struct {
dao.Boarder
}
func (mock1) AAA(_ string) (bool, error) { return true, nil }
func (mock1) BBB(_ string) error { return nil }
func (mock1) CCC(_ string) (string, error) { return "AAA", nil }
func Test_GetBoardIdByBoardName_OK(t *testing.T) {
var err error
var boardName string
thread := NewThread()
thread.SetBoardDao(&mock1{})
boardName, err = thread.getBoardIdByBoardName("")
if err != nil {
t.Error(err.Error())
}
if boardName != "AAA" {
t.Error("Board Name Must be AAA")
}
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
golang 1.5時代の知識で語ることを最初に断っておきます。
Goは型付けが厳密な言語なので、スタブ化に少し工夫が必要です。書かれているコードだと、Appが完全にDaoに依存しているため、このままではdummyにとばせません。
Daoを定義する際に、あらかじめテストの事を考えて、スタブ化したいメソッドを関数オブジェクトとしてメンバー変数に持つか、グローバル変数に持っておく必要があります。
例えば次の様にして、DaoのメソッドDを、修正することでダミー用関数Eに切り替えることができます。
// Dao
type Dao struct {
D func() // メンバ変数を関数として宣言しておく
}
//func (this *Dao) D() { Daoのメソッドではなく、普通の関数として定義する
func D() {
fmt.Println("D")
}
func E() { // 切り替え用の関数
fmt.Println("E")
}
// App
type App struct {
dao *Dao
}
func (this *App) SetDao(dao *Dao) {
this.dao = dao
}
func (this *App) A() {
this.dao.D()
}
func main() {
d := &Dao{}
d.D = D // 通常の関数を使用する
app := &App{dao: d}
app.A() // D が返る
d.D = E// 切り替え用の関数に切り替え
app.A()// Eが返る
}
E関数やそれをDaoのメンバーに代入している部分をテストコードで記載すれば、テスト時にのみEを呼ぶといったことが可能になります。
また、Daoのメンバーを小文字にしておけば、別パッケージからは見えなくなるので、ある程度は秘匿性も守られると思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.37%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる