Skip to content

yrichika/gest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

67 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gest

English documentation located is after Japanese documentation.

日本語ドキュメント

Introduction

GoのテストをJestのスタイルで書くことができるテストフレームワークです。 Goのテストと100%互換性があるため、2つの種類のテスト混在させて書くことができます(実態はGoのテストのラッパーです)。 そのため、Goテストを残しつつ、徐々にGestに切り替えていくことも可能です。 さらに、100%互換性を保つため、CIの際にGestをCIサーバーにインストールしなくても、go testだけでテストを実行することができます。

JavaScript/TypeScriptでJestを使ったことがある方は、Goに移行した際に、簡単にGestでユニットテストを書くことができます。

以下は、Gestで書いた簡単なサンプルです。

func TestSuiteGest(testingT *testing.T) {

  t := gt.CreateTest(testingT)

  t.Describe("testing Jest style in Go!", func() {

    t.It("should return true if input true", func() {
      r := someFunc(true)
      gt.Expect(v, &r).ToBe(true)
    })

    t.It("should return 2 if 1 given", func() {
      r := addOne(1)
      gt.Expect(v, &r).ToBe(2)
    })

    // 並行テスト
    t.Parallel().It("should execute test parallelly", func() {
      time.Sleep(3 * time.Second)
    })

    // このテストをスキップ
    t.Skip().It("should be skipped", func(){
      // this test will be skipped!
    })

    // 後で書くテストはTodoへ
    t.Todo("some test to write afterward")

  })
}

Gestコマンド

Gestでは、テストを実行する際に、gestコマンドを使います。ただし、go testを使っても、Gestで書いたテストをそのまま実行することも可能ですが、gestコマンドでテストを実行することで、よりわかりやすいテスト結果を得ることができます。

出力サンプル:

sample output

インストール方法

Go >= 1.21.5

gestコマンドとライブラリのインストール

# gestコマンドのインストール
go install github.com/yrichika/gest/cmd/gest@latest
# テスト用のライブラリのインストール
go get github.com/yrichika/gest/pkg/gt@latest

環境変数のGOBINが設定されていない場合は、GOBINの設定をしてください。そして、PATHGOBINのパスも含めてください。 GOBIN~/go/binにデフォルトで設定されていることが多いので、わからない方は~/go/binがあるか確認してみてください。

gestのライブラリインポート

gestをプロジェクト内で使う場合は、Gestのgtimportして使います

import (
  "testing"

  "github.com/yrichika/gest/pkg/gt"
)

func TestSuiteGest(testingT *testing.T) {
  // ...
}

使い方

テストの作成方法

Gestは、Goの標準のテスト関数の中にテストを作成します。

まずは、Gestのテストを実行するために、gt.CreateTest()で、*Testを取得してください。

// Goテストで使われる`t`と混同しないために、
// Test関数の引数は、`t *testing.T`とせずに、`testingT *testing.T`と
// 書くことをおすすめします。ただし、`t`と紛らわしくなければ、ここは完全に自由です。
func TestSuiteGest(testingT *testing.T) {
  // CreateTestに *testing.T を渡し、戻り値の *Test である`t`を取得します。
  t := gt.CreateTest(testingT)
}

テスト実行メソッド

Jestと少し違うところですが、Jestの場合は、describeitを自由にネストすることができますが、Gestでは基本的に、Describeの中のコールバック関数の中に、1階層のItメソッドを書く構造になります。Describeの中にDescribeItの中にItもしくは、Itの中にDescribeといったような、自由なネストをすることはできません。 ただし、1つのGoテスト関数の中に、複数のDescribeを書くことができ、Describeの中に、複数のItテストを書くことができます。 複数のDescribeを書く際は、その都度gt.CreateTestで、Testオブジェクトを作成してください。

その際に、必ずDescribeメソッドがItメソッドの外側になるように書いてください。逆に書いた場合はテストが実行されません。

また、Itだと表現しにくい場合は、Testメソッドによって表現を変えることができます。 ただし、処理の内容は同じです。

func TestSuiteGest(testingT *testing.T) {

  t1 := gt.CreateTest(testingT)

  // Describe()がテストの「外側」に
  t1.Describe("関数・テスト1の説明", func() {
    // It()はDescribe()の中に書いてください。
    t1.It("テスト1のテスト1", func() {
      // ...
    })

    t1.It("テスト1のテスト2", func() {
      // ...
    })

    // Itの代わりにTestを使うこともできます。
    t1.Test("テスト1のテスト2", func () {
      // ...
    })

  })

  // 新しいテスト(t2 *Test)を作成し、
  t2 := gt.CreateTest(testingT)
  // Describe()を複数実行することが可能です。
  t2.Describe("関数・テスト2の説明", func() {
    t2.It("テスト2のテスト内容", func() {
      // ...
    })
  })
}

BeforeAll, AfterAll, BeforeEach, AfterEach

テストの前処理、後処理のために、 BeforeAll, AfterAll, BeforeEach, AfterEach が用意されています。

  • BeforeAll: Describeメソッドの前に実行されます。
  • BeforeEach: 各Itメソッドの前に実行されます。
  • AfterEach: 各Itメソッドの後に実行されます。
  • AfterAll: Describeメソッドの後に実行されます。

以下は、BeforeEachの例です。他のメソッドも、実行されるタイミングが違うだけで、書き方は同じです。

func TestSuiteGest(testingT *testing.T) {

  t := gt.CreateTest(testingT)

  t.BeforeEach(func () {
    // テストに必要な前処理を書きます
  })

  t.Describe("", func() {
    // 各Itが実行される前に、BeforeEachの処理が実行されます。
    t.It("", func() {
      // some test
    })

  })
}

Prl, Parallel

並行テストを実行する場合は、トップレベルでは、t.Prl()を使うか、もしくはtestingT.Parallel()を直接呼び出してください。 t.Prl()は、testingT.Parallel()のエイリアスです。

Describeの中のテスト(Runレベルのサブテスト)では、t.Parallel.It(...)という書き方で並行テストを実行できます。

func TestSuiteGest(testingT *testing.T) {

  t := gt.CreateTest(testingT)
  // トップレベルで並行テストにする場合は、`Prl()`を使います。
  t.Prl() // もしくは、testingT.Parallelでも構いません。

  t.Describe("Parallel testing", func() {
    // `It`の前に`Parallel()`をつけることで、`Run`レベルでの並行テストになります。
    t.Parallel().It("is parallelly executed", func() {
      // ...
    })
  })
}

アサーション(Expect)

テスト結果をアサーとする場合は、Expect関数と、アサート用のメソッドを使います。ToBeや、ToDeepEqualなどのメソッドを使って、結果の値をアサーとします。 ExpectExpect[A any](test *Test, actual *A)というシグニチャになっています。そのため、第一引数には、tを渡し、第二引数には確認したい値のポインタを渡してください。

プリミティブ型であれば、ToBeだけで、その等価性をアサートできます。

func TestAssertions(testingT *testing.T) {
  t := gt.CreateTest(testingT)

  t.Describe("assertion sample", func() {
    t.It("should return true", func() {
      r1 := someFunc()
      expected1 = true
      // プリミティブ型 int, bool, string, ...etc はすべて`ToBe`だけでアサート可能です
      gt.Expect(t, &r1).ToBe(expected1)

      r2 := otherFunc()
      expected2 = 13
      gt.Expect(t, &r2).ToBe(expected2)
    })
  })
}

gestコマンドでテスト実行

go.modのあるプロジェクトルートで実行することをおすすめします。ただし、必須ではありません。 gestコマンドは、コマンドの実行ディレクトリから再帰的に_test.goで終わるファイル名を探し、それらすべてのテストを実行します。 プロジェクトルート以外で実行した場合は、そのディレクトリ以下のファイルを再帰的に検索し、_test.goファイルのテストをすべて実行します。 1つだけのテストを実行したい場合は、以下の-runオプションを使用してください。

gest
-run

-runの後に、テスト関数の名前を指定してください。-runの後の引数に渡した文字列は、Regexでマッチするテストをすべて実行します。

gest -run TestFunctionNameToRun
-v

gestの出力に加え、go testで実行した場合の出力も表示します。 gestでは、基本的には余分なコンソールへの出力を抑えるため、特定の文字列を出力しない仕組みになっています。 そのため、場合によっては、テスト内でデバッグ用にprintlnなどを使った場合、出力がコンソールにされないことがあります。 そういった際に、-vオプションを付けると、デバッグ用に出力したい文字列も全て出力されます。

gest -v
-vv

gestの出力に加え、go test -vで実行した場合の出力も表示します。

gest -vv
-cover

HTMLのカバレッジファイルをgest_coverageディレクトリに作成します。 カバレッジファイルを出力するディレクトリ名を指定したい場合は、-coverprofileオプションを使用します。

カバレッジ出力の場合のみ、go.modのあるプロジェクトルートで実行することが必須になります。go.modがないディレクトリで、gest -coverを実行するとエラーになります。

gest -cover
# OR 出力先のディレクトリ名を指定する場合
gest -cover -coverprofile=[any_dir_name]
-all-dirs

デフォルトで、Gestはexamplesフォルダのテストを実行しません。 もしexamplesフォルダの中のテストを実行したい場合、もしくはexamplesという名前をつけたサブディレクトリの中にテストがある場合は、-all-dirsを指定することで、全てのディレクトリ内のファイルがテストの対象になります。

gest -all-dirs
# -run と組み合わせることもできます
gest -all-dirs -run TestInExamplesDir

Assertions

ToBe(T)

これで、int系、bool, string, complex64, etc、プリミティブ型とtime.Timetime.Durationstruct型、スライス、配列、mapはすべてアサートすることができます。

構造体が同じかどうかの確認は、内部の処理では、reflect.DeepEqual()を使って、2つの値の比較を行っています。

gt.Expect(t, &r).ToBe(true)

// 構造体の場合
expected := User{Name: "john", Age: 10}
gt.Expect(t, &r).ToBe(expected)

ToBeSamePointerAs(*T)

ポインタが同じかテストすることができます。

a := 1
p := &a
gt.Expect(t, &a).ToBeSamePointerAs(p)

ToBeNil()

値がnilかどうかをアサートします。

var nilValue *int = nil
gt.Expect(t, nilValue).ToBeNil()

ToBeNilInterface()

interface型の値がnilかどうかをアサートするには、ToBeNil()ではなく、ToBeNilInterfaceを使ったください。 例えば、error型がnilかどうかを確認する場合に使います。 interfaceであるerror型などは、ToBeNil()では正しくアサートすることができません。

var err error
// errがnilの場合
gt.Expect(t, &err).ToBeNilInterface()

ToMatchRegex(string)

文字列が正規表現と一致するかアサートします。

str := "foo"
gt.Expect(t, &str).ToMatchRegex("^foo$")

ToContainString(string)

Actualに指定した文字列が含まれるかアサートします。

str := "hello, world"
gt.Expect(t, &str).ToContainString("world")

ToBe_(func(T) bool)

ToBe_が付いたToBe_は第一引数に、比較する関数を取り、actualの値とexpectedの値を比較します。 「~以上」や、「~以下」のように、2つの値を比較したい場合に使います。 gt.GreaterThanなどの比較用関数は用意されているので、比較用関数は自作しなくても大丈夫です。 ただし、自作して、引数に渡すことも可能です。

val := 10
gt.Expect(t, &val).ToBe_(gt.GreaterThan(1))
gt.Expect(t, &val).ToBe_(gt.GreaterThanOrEq(10))
gt.Expect(t, &val).ToBe_(gt.LessThan(11))
gt.Expect(t, &val).ToBe_(gt.LessThanOrEq(10))
// Betweenに指定した数値(min)と、expectedに指定した値(max)の間にある場合はpassします
gt.Expect(t, &val).ToBe_(gt.Between(1, 10))
// time.Duration
duration := 2 * time.Second
gt.Expect(t, &duration).ToBe_(gt.GreaterThan(1 * time.Second))

// 時間を比較する際は、After, Beforeを使います
time := time.Now()
past := time.Now().Add(-1 * time.Minute)
future := time.Now().Add(1 * time.Minute)
gt.Expect(t, &time).ToBe_(gt.After(past))
gt.Expect(t, &time).ToBe_(gt.AfterOrEq(past))
gt.Expect(t, &time).ToBe_(gt.Before(future))
gt.Expect(t, &time).ToBe_(gt.BeforeOrEq(future))
// TimeBetweenに指定した時間(from)と、expectedに指定した値(to)の間にある場合はpass
gt.Expect(t, &time).ToBe_(gt.TimeBetween(past, future))

ToBeType(func (*T) bool)

値の型が、どの型であるかをアサートすることができます。 引数に渡すコールバック関数は、型をアサートするための関数です。 これらは、すでに用意されているので、そのまま使ってください

  • OfBool
  • OfInt
  • OfInt16
  • OfInt32
  • OfString
  • OfArray
  • OfMap
  • ...etc

基本的な型のほとんどが用意されています

boolVal := true
gt.Expect(t, &boolVal).ToBeType(gt.OfBool)

intVal := 1
gt.Expect(t, &intVal).ToBeType(gt.OfInt)

ToBeIn([]T)

ある要素がスライスに含まれているかをアサートします。 プリミティブ型(comparable)と、time.Durationtime.Timeと、struct型の値とスライスをアサートすることができます。

intSlice := []int{1, 2, 3, 4, 5}
val := 3
gt.Expect(t, &val).ToBeIn(intSlice)

Not()

Not()をアサーションの前につけることで、逆の意味のアサーションを行います。

gt.Expect(t, &r).Not().ToBe(true)
gt.Expect(t, &x).Not().ToBeNil()
// ...etc

LogWhenFail(string).Expect(*A)

アサートエラーが起きた場合は、自動的に適切なメッセージを出力しますが、独自のメッセージに変更したい場合は、LogWhenFail関数を使います。 LogWhenFailを使う場合は、t *Testは、Expectではなく、LogWhenFailの第一引数に指定してください。

a := 1
b := 2
gt.LogWhenFail[int](t, "Fail時に出力されるカスタムメッセージ").Expect(&a).ToBe(b)

ExpectPanic(*Test).ToHappen(func())

panicが起きたかどうかをアサートする場合は、ExpectPanic関数を使います。 ExpectPanicでも、Not()を使い、panicが起きなかったことをアサートすることができます。

// panicが起きたことをアサート
gt.ExpectPanic(t).ToHappen(func() {
  panic("panic")
})

// panicが起きないことをアサート
gt.ExpectPanic(t).Not().ToHappen(func () {
  someFuncMightPanic()
})

TODO:

その他のアサーションは現在実装中です。 基本的には、Jestと同じアサーションと、Go独自に必要そうなアサーションを追加していきます。

English Documentation

WIP: I'm working on it!

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages