質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.47%

  • Go

    544questions

    Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

  • テスト駆動開発

    65questions

    テスト駆動開発は、 プログラム開発手法の一種で、 プログラムに必要な各機能をテストとして書き、 そのテストが動作する必要最低限な実装を行い コードを洗練させる、といったサイクルを繰り返す手法の事です。

Golangで単体テストコードの実装方法

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 1,282

kappazushi

score 7

前提・実現したいこと

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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+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のメンバーを小文字にしておけば、別パッケージからは見えなくなるので、ある程度は秘匿性も守られると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

関連した質問

同じタグがついた質問を見る

  • Go

    544questions

    Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

  • テスト駆動開発

    65questions

    テスト駆動開発は、 プログラム開発手法の一種で、 プログラムに必要な各機能をテストとして書き、 そのテストが動作する必要最低限な実装を行い コードを洗練させる、といったサイクルを繰り返す手法の事です。