Check
GitHub概要
スター696
ウォッチ14
フォーク182
作成日:2014年4月1日
言語:Go
ライセンス:Other
トピックス
なし
スター履歴
データ取得日時: 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()
}