Check

テストフレームワークGoGolangアサーションテストスイートフィクスチャBDD

GitHub概要

go-check/check

Rich testing for the Go language

スター696
ウォッチ14
フォーク182
作成日:2014年4月1日
言語:Go
ライセンス:Other

トピックス

なし

スター履歴

go-check/check Star History
データ取得日時: 2025/10/22 08:07

ライブラリ

Check

概要

CheckはGo言語向けのリッチなテストフレームワークで、標準のtestingパッケージを拡張してより豊富な機能を提供します。「gocheck」としても知られ、Go言語の標準テストライブラリの限定的な機能を補完し、アサーション、テストスイート、フィクスチャ、一時ディレクトリ管理など、他の言語の成熟したテストフレームワークに匹敵する機能を提供。Gustavo Niemeyerによって開発され、JUnitやRSpecのような洗練されたテスト体験をGoプログラマーに提供することを目的としています。

詳細

Check 2025年版は、Go言語における包括的なテストソリューションとして、標準のgo testランナーとシームレスに統合されます。主要機能として、AssertとCheckメソッドによる即座の失敗/継続制御、深い型比較、正規表現マッチング、Equals・NotNil・Matches・PanicMatchesなどの豊富なチェッカー、テストスイートベースのグループ化、SetUpSuite・SetUpTest・TearDownTest・TearDownSuiteによるフィクスチャ管理を提供。ベンチマークテストの統合、パニックキャッチング、詳細なエラーレポート、テストスキップ機能、期待される失敗のサポート、マルチライン文字列レポート、失敗時のコメント表示など、プロフェッショナルなテスト開発に必要な機能を網羅しています。

主な特徴

  • 豊富なアサーション機能: Equals、Matches、IsNil、ErrorMatchesなど多様なチェッカー
  • テストスイート管理: 構造化されたテストの組織化とグループ化
  • フィクスチャサポート: スイート/テストレベルのセットアップ・ティアダウン
  • 優れたエラー報告: 失敗箇所の詳細な情報と差分表示
  • ベンチマーク統合: 同一フレームワーク内でのパフォーマンステスト
  • 標準testingとの完全互換: 既存のgo testワークフローにシームレス統合

メリット・デメリット

メリット

  • 標準testingパッケージより豊富な機能セット
  • JUnit/RSpecユーザーに馴染みやすいAPI設計
  • テストスイートによる効率的なテスト組織化
  • フィクスチャによる共通セットアップの再利用
  • 詳細で読みやすいエラーメッセージ
  • 既存のGo testツールチェーンとの完全互換性
  • パニック処理とリカバリの自動化

デメリット

  • 2020年以降アクティブな開発が停滞
  • 標準ライブラリのみで十分な場合は過剰
  • 学習曲線が標準testingより急
  • モダンなGo開発では標準testing + testifyが主流
  • コミュニティサポートが減少傾向
  • 新しいGoバージョンへの対応が遅れがち

参考ページ

書き方の例

インストールと基本セットアップ

# Goモジュールにcheckパッケージを追加
go get gopkg.in/check.v1

# 最新版を確実に取得
go get -u gopkg.in/check.v1

基本的なテストスイートの作成

package mypackage_test

import (
	"testing"
	"io"
	. "gopkg.in/check.v1"
)

// go testランナーにgocheckをフック
func Test(t *testing.T) { TestingT(t) }

// テストスイート構造体の定義
type MySuite struct{}

// スイートの登録
var _ = Suite(&MySuite{})

// 基本的なテストメソッド
func (s *MySuite) TestHelloWorld(c *C) {
	// Assert: 失敗時に即座にテスト停止
	c.Assert(42, Equals, 42)
	c.Assert("hello", Equals, "hello")
	
	// Check: 失敗してもテスト継続
	c.Check(10, Equals, 10)
	c.Check("world", Equals, "world")
	
	// エラーマッチング
	c.Assert(io.ErrClosedPipe, ErrorMatches, "io: .*on closed pipe")
	
	// nilチェック
	var ptr *string
	c.Assert(ptr, IsNil)
	
	// コメント付きアサーション
	value := 100
	c.Assert(value, Equals, 100, Commentf("値は%dであるべき", 100))
}

フィクスチャの使用

type DatabaseSuite struct {
	db       *sql.DB
	testData string
	tmpDir   string
}

// スイート全体の初期化(一度だけ実行)
func (s *DatabaseSuite) SetUpSuite(c *C) {
	// データベース接続の初期化
	db, err := sql.Open("postgres", "test_db_url")
	c.Assert(err, IsNil)
	s.db = db
	
	// スキーマの作成
	_, err = s.db.Exec(`CREATE TABLE users (
		id SERIAL PRIMARY KEY,
		name VARCHAR(100),
		email VARCHAR(100) UNIQUE
	)`)
	c.Assert(err, IsNil)
}

// 各テスト前の準備(テストごとに実行)
func (s *DatabaseSuite) SetUpTest(c *C) {
	// 一時ディレクトリの作成
	s.tmpDir = c.MkDir()
	
	// テストデータの投入
	s.testData = "test_user_123"
	_, err := s.db.Exec(
		"INSERT INTO users (name, email) VALUES ($1, $2)",
		s.testData, "[email protected]",
	)
	c.Assert(err, IsNil)
}

// 各テスト後のクリーンアップ
func (s *DatabaseSuite) TearDownTest(c *C) {
	// データのクリーンアップ
	_, err := s.db.Exec("DELETE FROM users WHERE name = $1", s.testData)
	c.Assert(err, IsNil)
	
	// 一時ディレクトリは自動削除される
}

// スイート全体の終了処理
func (s *DatabaseSuite) TearDownSuite(c *C) {
	// テーブルの削除
	_, err := s.db.Exec("DROP TABLE users")
	c.Assert(err, IsNil)
	
	// データベース接続のクローズ
	s.db.Close()
}

// フィクスチャを使用したテスト
func (s *DatabaseSuite) TestUserOperations(c *C) {
	// ユーザー検索テスト
	var count int
	err := s.db.QueryRow("SELECT COUNT(*) FROM users WHERE name = $1", s.testData).Scan(&count)
	c.Assert(err, IsNil)
	c.Assert(count, Equals, 1)
	
	// 一時ディレクトリの使用
	testFile := filepath.Join(s.tmpDir, "test.txt")
	err = ioutil.WriteFile(testFile, []byte("test content"), 0644)
	c.Assert(err, IsNil)
	
	// ファイルが存在することを確認
	_, err = os.Stat(testFile)
	c.Assert(err, IsNil)
}

高度なアサーションとマッチャー

func (s *MySuite) TestAdvancedAssertions(c *C) {
	// 文字列マッチング
	result := "Hello, World!"
	c.Assert(result, Matches, "Hello.*")
	c.Assert(result, Matches, ".*World!")
	
	// Not系アサーション
	c.Assert(42, Not(Equals), 43)
	c.Assert("hello", Not(Matches), "^world")
	
	// DeepEquals: 構造体やスライスの深い比較
	type User struct {
		ID   int
		Name string
		Tags []string
	}
	
	user1 := User{ID: 1, Name: "Alice", Tags: []string{"admin", "user"}}
	user2 := User{ID: 1, Name: "Alice", Tags: []string{"admin", "user"}}
	c.Assert(user1, DeepEquals, user2)
	
	// Panic検証
	c.Assert(func() { panic("test panic") }, PanicMatches, "test panic")
	c.Assert(func() { panic(42) }, Panics, 42)
	
	// 長さチェック
	slice := []int{1, 2, 3, 4, 5}
	c.Assert(slice, HasLen, 5)
	
	// 型チェック
	var iface interface{} = "string"
	c.Assert(iface, FitsTypeOf, "")
	
	// カスタムチェッカーの使用
	c.Assert(100, satisfies, func(n int) bool { return n > 50 })
}

// カスタムチェッカーの実装
type satisfiesChecker struct {
	*CheckerInfo
}

var satisfies Checker = &satisfiesChecker{
	&CheckerInfo{Name: "Satisfies", Params: []string{"value", "func"}},
}

func (checker *satisfiesChecker) Check(params []interface{}, names []string) (bool, string) {
	f := params[1].(func(int) bool)
	v := params[0].(int)
	if f(v) {
		return true, ""
	}
	return false, "Value does not satisfy the condition"
}

ベンチマークテストの統合

func (s *MySuite) BenchmarkStringConcat(c *C) {
	// ベンチマークもフィクスチャを利用可能
	for i := 0; i < c.N; i++ {
		result := ""
		for j := 0; j < 100; j++ {
			result += "a"
		}
	}
}

func (s *MySuite) BenchmarkStringBuilder(c *C) {
	for i := 0; i < c.N; i++ {
		var builder strings.Builder
		for j := 0; j < 100; j++ {
			builder.WriteString("a")
		}
		_ = builder.String()
	}
}

// ベンチマーク実行コマンド
// go test -check.b

テストの選択的実行とスキップ

// フラグによるテストスキップ
var integration = flag.Bool("integration", false, "Include integration tests")

type IntegrationSuite struct{}

func (s *IntegrationSuite) SetUpSuite(c *C) {
	if !*integration {
		c.Skip("-integration フラグが指定されていません")
	}
}

func (s *IntegrationSuite) TestExternalAPI(c *C) {
	// 外部APIを使用する統合テスト
	// このテストは-integrationフラグがある場合のみ実行
}

// 条件付きスキップ
func (s *MySuite) TestPlatformSpecific(c *C) {
	if runtime.GOOS != "linux" {
		c.Skip("このテストはLinuxでのみ実行可能です")
	}
	// Linux固有のテストコード
}

// 特定のテストのみ実行
// go test -check.f "TestHelloWorld"
// go test -check.f "MySuite"
// go test -check.f "MySuite.Test.*"

詳細なログ出力とデバッグ

func (s *MySuite) TestWithLogging(c *C) {
	// ログ出力(-check.vまたは-check.vvで表示)
	c.Log("テスト開始")
	
	values := []int{1, 2, 3, 4, 5}
	for i, v := range values {
		c.Logf("処理中: index=%d, value=%d", i, v)
		
		if v%2 == 0 {
			c.Logf("偶数を発見: %d", v)
		}
	}
	
	// エラー時の詳細情報
	expected := map[string]int{"a": 1, "b": 2, "c": 3}
	actual := map[string]int{"a": 1, "b": 20, "c": 3}
	
	// DeepEqualsは差分を詳細に表示
	c.Assert(actual, DeepEquals, expected)
}

// 実行オプション
// go test -check.v    # 詳細モード(成功したテストも表示)
// go test -check.vv   # 超詳細モード(ログキャッシュ無効化)

実践的な例:HTTPハンドラーのテスト

type HTTPSuite struct {
	server *httptest.Server
	client *http.Client
}

func (s *HTTPSuite) SetUpSuite(c *C) {
	// テストサーバーの起動
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/users":
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode(map[string]string{
				"id": "123",
				"name": "Test User",
			})
		case "/api/error":
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("Internal Server Error"))
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	})
	
	s.server = httptest.NewServer(handler)
	s.client = &http.Client{Timeout: 5 * time.Second}
}

func (s *HTTPSuite) TearDownSuite(c *C) {
	s.server.Close()
}

func (s *HTTPSuite) TestAPIEndpoints(c *C) {
	// 成功レスポンステスト
	resp, err := s.client.Get(s.server.URL + "/api/users")
	c.Assert(err, IsNil)
	c.Assert(resp.StatusCode, Equals, http.StatusOK)
	
	var user map[string]string
	err = json.NewDecoder(resp.Body).Decode(&user)
	c.Assert(err, IsNil)
	c.Assert(user["id"], Equals, "123")
	c.Assert(user["name"], Equals, "Test User")
	resp.Body.Close()
	
	// エラーレスポンステスト
	resp, err = s.client.Get(s.server.URL + "/api/error")
	c.Assert(err, IsNil)
	c.Assert(resp.StatusCode, Equals, http.StatusInternalServerError)
	
	body, err := ioutil.ReadAll(resp.Body)
	c.Assert(err, IsNil)
	c.Assert(string(body), Equals, "Internal Server Error")
	resp.Body.Close()
	
	// 404テスト
	resp, err = s.client.Get(s.server.URL + "/api/notfound")
	c.Assert(err, IsNil)
	c.Assert(resp.StatusCode, Equals, http.StatusNotFound)
	resp.Body.Close()
}