Goでの単体テストと統合テストの分離


97

GoLang(testify)で単体テストと統合テストを分離するための確立されたベストプラクティスはありますか?ユニットテスト(外部リソースに依存しないため非常に高速に実行される)と統合テスト(外部リソースに依存するため実行速度が遅い)が混在しています。ですから、統合テストを含めるかどうかを制御できるようにしたいのですgo test

最も簡単な方法は、mainで-integrateフラグを定義することです。

var runIntegrationTests = flag.Bool("integration", false
    , "Run the integration tests (in addition to the unit tests)")

そして、すべての統合テストの先頭にifステートメントを追加するには:

if !*runIntegrationTests {
    this.T().Skip("To run this test, use: go test -integration")
}

これは私ができる最善のことですか?私はtestifyのドキュメントを検索して、おそらく命名規則やこれを実現する何かがあるかどうかを確認しましたが、何も見つかりませんでした。何か不足していますか?


2
stdlibは-shortを使用して、ネットワーク(および他の長時間実行されているもの)を攻撃するテストを無効にすると思います。そうでなければ、あなたの解決策は大丈夫に見えます。
Volker

-shortは、カスタムビルドフラグと同様に適切なオプションですが、フラグをメインにする必要はありません。varをvar integration = flag.Bool("integration", true, "Enable integration testing.")関数の外部として定義すると、変数はパッケージスコープに表示され、フラグは正しく機能します
Atifm

回答:


155

@ Ainar-Gは、テストを分離するためのいくつかの優れたパターンを提案しています。

SoundCloudのこのGoプラクティスセットでは、ビルドタグビルドパッケージの「ビルド制約」セクションで説明)を使用して、実行するテストを選択することを推奨しています。

integration_test.goを記述し、それに統合のビルドタグを付けます。サービスアドレスや接続文字列などの(グローバル)フラグを定義し、テストで使用します。

// +build integration

var fooAddr = flag.String(...)

func TestToo(t *testing.T) {
    f, err := foo.Connect(*fooAddr)
    // ...
}

go testはgo buildと同じようにビルドタグを取得するため、を呼び出すことができますgo test -tags=integration。また、flag.Parseを呼び出すパッケージmainを合成するため、宣言されて表示されるフラグはすべて処理され、テストで使用できます。

同様のオプションとして、ビルド条件を使用してデフォルトで統合テストを実行し// +build !unit、を実行してオンデマンドで無効にすることもできますgo test -tags=unit

@adamcコメント:

ビルドタグを使用しようとする他の人にとっては、// +build testコメントがファイルの最初の行であり、コメントの後に空白行を含めることが重要です-tags。それ以外の場合、コマンドはディレクティブを無視します。

また、アンダースコアを使用できますが、ビルドコメントで使用されるタグにダッシュを含めることはできません。たとえば、// +build unit-tests動作しませんが、動作し// +build unit_testsます。


1
私はこれをしばらくの間使用しており、これははるかに最も論理的で単純なアプローチです。
Ory Band

1
同じパッケージに単体テストがある場合は、単体テストで設定// + build unitし、テストを実行するために-tagユニットを使用する必要があります
LeoCBS

2
@ Tyler.z.yangリンクまたはタグの廃止に関する詳細を提供できますか?そのような情報は見つかりませんでした。答えで説明されている方法と、テストで型と関数をモックするために、go1.8でタグを使用しています。それは私が思うインターフェースの良い代替手段です。
Alexander I.Grafov

2
ビルドタグの使用を試みる他の人にとって、// +buildテストコメントがファイルの最初の行であり、コメントの後に空白行を含めることが重要です-tags。それ以外の場合、コマンドはディレクティブを無視します。また、アンダースコアを使用できますが、ビルドコメントで使用されるタグにダッシュを含めることはできません。たとえば、// +build unit-tests動作しませんが、動作し// +build unit_testsます
adamc 2017

6
ワイルドカードの扱い方は?go test -tags=integration ./...動作し
ません。

53

@ Ainar-Gの優れた答えに対するコメントを詳しく説明するために、私はこの1年間-shortIntegration命名規則との組み合わせを使用して、両方の世界のベストを達成しています。

単体テストと統合テストの調和、同じファイル内

ビルドフラグは、以前に複数のファイル(持っている私を余儀なくさservices_test.goservices_integration_test.goなど)。

代わりに、以下の例を見てください。最初の2つは単体テストで、最後に統合テストがあります。

package services

import "testing"

func TestServiceFunc(t *testing.T) {
    t.Parallel()
    ...
}

func TestInvalidServiceFunc3(t *testing.T) {
    t.Parallel()
    ...
}

func TestPostgresVersionIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }
    ...
}

最後のテストには次の規則があることに注意してください。

  1. Integrationテスト名で使用します。
  2. -shortフラグディレクティブで実行されているかどうかを確認します。

基本的に、仕様は次のとおりです。「すべてのテストを通常どおりに記述します。長時間実行するテスト、または統合テストの場合は、この命名規則に従って-short、ピアにとって適切であることを確認してください。」

単体テストのみを実行します。

go test -v -short

これにより、次のような一連のメッセージが表示されます。

=== RUN   TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
        service_test.go:138: skipping integration test

統合テストのみを実行:

go test -run Integration

これは、統合テストのみを実行します。本番環境で煙をテストするカナリアに役立ちます。

明らかにこのアプローチの欠点は、誰かがフラグgo testなしで実行した場合-short、デフォルトですべてのテスト(単体テストと統合テスト)を実行することです。

実際には、プロジェクトが単体テストと統合テストを行うのに十分な大きさMakefileである場合は、単純なディレクティブを使用できる場所を使用している可能性がありますgo test -short。または、README.mdファイルに入れて、その日を呼び出します。


3
シンプルさが大好き
ジェイコブスタンレー

そのようなテスト用に個別のパッケージを作成して、パッケージのパブリック部分のみにアクセスしますか?または、すべて混在していますか?
Dr.eel、

@ Dr.eelまあ、それは答えからのOTです。しかし、個人的には、私は両方を好みます。テスト用に別のパッケージ名を使用しimportて、パッケージとそれに対してテストできるようにします。これにより、APIが他の人にどのように見えるかがわかります。次に、内部テストパッケージ名としてカバーする必要がある残りのロジックをフォローアップします。
eduncan911

@ eduncan911回答ありがとうございます!ここで理解しているようpackage servicesに、統合テストの要領が含まれているので、パッケージのAPIfoをブラックボックスとしてテストするにはpackage services_integration_test、内部構造を操作する機会を与えないように、別の方法で名前を付ける必要があります。したがって、単体テスト(内部にアクセスする)のパッケージはという名前にする必要がありますpackage services。そうですか?
Dr.eel、

はい、そうです。ここで私はそれを行う方法のきれいな例です:github.com/eduncan911/podcast(予告例を用いて100%のコードカバレッジ、)
eduncan911

50

3つの解決策が考えられます。1つ目は、単体テストにショートモードを使用することです。したがってgo test -short、単体テストと同じように使用しますが-short、統合テストを実行するためのフラグは使用しません。標準ライブラリは、shortモードを使用して、長時間実行されるテストをスキップするか、より単純なデータを提供してテストをより高速に実行します。

2つ目は、規則を使用してテストを呼び出すか、TestUnitFooまたはテストフラグTestIntegrationFoo使用して、実行するテストを示すことです。したがって、単体テストと統合テストに使用します。-rungo test -run 'Unit'go test -run 'Integration'

3番目のオプションは、環境変数を使用し、を使用してテスト設定でそれを取得することos.Getenvです。次に、go test単体テストとFOO_TEST_INTEGRATION=true go test統合テストにsimple を使用します。

個人的には、この-shortソリューションがよりシンプルで標準ライブラリで使用されているため、長期実行テストを分離/簡素化する事実上の方法のように思えます。しかし、-runおよびos.Getenvソリューションはより高い柔軟性を提供します(正規表現がに含まれるため、さらに注意が必要です-run)。


1
Tester-GoIDE(Atom、Sublimeなど)に共通のコミュニティテストランナー(など)には、他の-shortフラグと共に実行する組み込みオプションが-coverageあります。したがって、テスト名には両方の統合を組み合わせて使用​​し、if testing.Short()それらのテスト内のチェックも使用します。-shortIDE内で実行し、統合テストのみを明示的に実行するgo test -run "Integration"
eduncan911

5

私は最近同じことの解決策を見つけようとしていました。これらは私の基準でした:

  • 解決策は普遍的でなければならない
  • 統合テスト用の個別のパッケージはありません
  • 分離は完了する必要があります(統合テストのみを実行できるはずです)
  • 統合テストに特別な命名規則はありません
  • 追加のツールがなくてもうまく機能するはずです

前述のソリューション(カスタムフラグ、カスタムビルドタグ、環境変数)は、実際には上記の基準をすべて満たしていなかったため、少し掘り下げて試した後、このソリューションを思いつきました。

package main

import (
    "flag"
    "regexp"
    "testing"
)

func TestIntegration(t *testing.T) {
    if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) {
        t.Skip("skipping as execution was not requested explicitly using go test -run")
    }

    t.Parallel()

    t.Run("HelloWorld", testHelloWorld)
    t.Run("SayHello", testSayHello)
}

実装は簡単で最小限です。テストには単純な規則が必要ですが、エラーが発生しにくくなります。さらに改善するには、ヘルパー関数にコードをエクスポートします。

使用法

プロジェクト内のすべてのパッケージに対してのみ統合テストを実行します。

go test -v ./... -run ^TestIntegration$

すべてのテストを実行します(定期および統合):

go test -v ./... -run .\*

通常のテストのみを実行します。

go test -v ./...

このソリューションはツールがなくてもうまく機能しますが、Makefileまたはいくつかのエイリアスを使用すると、より使いやすくなります。また、goテストの実行をサポートする任意のIDEに簡単に統合できます。

完全な例はここにあります:https : //github.com/sagikazarmark/modern-go-application

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.