testifyパッケージのassert/requireとは？ [Go]

Goのテストケースを実装した際に、testifyパッケージのrequireとasserを使ったのですが、あまり違いがわからなかったことありませんか？

今回は、Goのテストコードでよく使われる`testify/assert`パッケージと`testify/require`パッケージの違いと、それぞれのメソッドについて調べてみようと思います。

https://github.com/stretchr/testify

# assertとrequireはどのような役割をしているのか

この二つは、testifyパッケージに入っているテスト用ツールです。

違いは以下のようになっています。

|パッケージ名|違い
|---|---
|assert|テストが失敗しても実行し続ける
|require|テストが失敗したら即時終了

主な違いはテストが失敗しても実行し続けるか終了するかの違いになっています。



# インストール

下のコマンドで、go.modに依存関係を追加してください。

```go:zsh
go get github.com/stretchr/testify
```

# assertとrequireの使い方

assertとrequireは基本的の同じメソッドを持っていて、主なメソッド名と機能は下のようになっています。

## 値と型の比較

| メソッド   | 機能                                   |
|------------|----------------------------------------|
| Equal      | 値を比較                               |
| EqualValues| 型関係なしに値が同じか比較             |
| Exactly    | 型があっているかどうかまで厳密に比較   |
| IsType     | 同じ型か比較                           |


### Equal
`Equal()`では、値の確認が行えます。

```go:go
func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
```

```go:go
assert.Equal(t, 123, 123) //-> Success
assert.Equal(t, 123, 321) //-> Fail
```

### EqualValues
`EqualValues()`では、型関係なしに値が同じか確認できます。

例えば下のようにuint32とint32のように、整数ではあるけれども実際には型が異なっていても、値が同じか確認することができます。

```go:go
assert.EqualValues(t, uint32(123), int32(123)) //-> Success
```

### Exactly
`Equal()`の厳密版のようなメソッドで、型があっているかどうかまで厳密に比較してくれます。
```go;go
func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
```

```go:go
assert.Exactly(t, int32(123), int64(123))//-> Success
```

### IsType

型が同じかどうか比較してくれます。

```go:go
type A struct{
    a sting
}

type B struct{
    b stirng
}
a := A("1")
b := B("1")
a2 := A("2")

assert.IsType(t, a, a2) //-> Success

assert.IsType(t, a, b)  //-> Fail

```

## リストと要素の比較

| メソッド     | 機能                           |
|--------------|--------------------------------|
| ElementMatch | リストの比較                   |
| Len          | スライスの要素数を検証           |
| Contains     | 含まれるかどうか検証           |
| NotContains  | 含まれないことを検証           |

### ElementMatch
listの比較が行えます。
返ってきたlistが思い通りの結果かどうかの確認に使用できます。
```go:go
func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool)
```

```go
assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) //-> Fail
```

### Len
スライスの要素数を検証できます。

スライスを返すメソッドで、想定されている数の要素数が返ってきているかを確認します。

```go:go
myslice := make([]int,3)

assert.Len(t, mySlice, 3)　//-> Success
assert.Len(t, mySlice, 4)　//-> Fail

```

### Contains

スライスやmap等で特定のものが含まれているかを確認できます。

```go:go
a.Contains(["Hello", "World"], "World")　//-> Success
```

### NotContains

Containsとは逆で、含まれていないかを確認できます。

```go:go
a.NotContains("Hello World", "Earth")　//-> Success
```


## JSONの比較

| メソッド | 機能                      |
|----------|---------------------------|
| JSONEq   | JSONが同じかどうか比較   |

### JSONeq
JSONが同じかどうか比較できます。

```go:go
assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
```

## エラーの検証

| メソッド   | 機能                                   |
|------------|----------------------------------------|
| EqualError | 返されたエラーが同じかどうか比較       |
| Error      | エラーが返されるか検証                 |
| NoError    | エラーがないことを検証                 |

### EqualError

エラーの内容を比較することができます。

`nil`でも比較することが可能です。

```go:go
actualObj, err := SomeFunction()
assert.EqualError(t, err,  expectedErrorString)
```

### Error

エラーが同じかどうかを確認できます。

エラーを自作した時とかに使えそうですね。

```go:go
actualObj, err := SomeFunction()
if assert.Error(t, err) {
	assert.Equal(t, expectedError, err)
}
```

### NoError

こちらはエラーがないとき(nil)かどうかを判断できます。

```go:go
actualObj, err := SomeFunction()
  if assert.NoError(t, err) {
	   assert.Equal(t, expectedObj, actualObj)
  }
```

## その他の検証

| メソッド | 機能                      |
|----------|---------------------------|
| Empty    | 値が空か検証              |
| False    | falseかどうか検証         |
| NotNil   | nil(null)でないかを検証   |

### Empty

値が空かを検証できます。
空と判定されるのは,`nil`,`0`,`""`,`false`です。

```go:go
a.Empty(obj)
```

### False

値がFalseかどうかを検証できます。

Emptyではなく、Falseかどうかを確認したいという目的の時はこちらの方が良さそうです。
```go:go
assert.False(t, myBool)
```

### NotNil

nilではないかを検証できます。

Emptyと似ていますが、コンテキストによってはこちらを採用する方が良いかもしれません。
```go:go
assert.NotNil(t, err)
```


# assertを使ってみる

assertを使ってみようと思います。

実際に下のように実装してみました。

```go:go
task := entity.Todo{
	Task:     "test",
	Deadline: time,
}

mrTodo.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(&entity.Todo{Task: "test", Done: false, Deadline: time}, nil)

taskRes, err := usc.Create(ctx, &task)

assert.NoError(t, err)
assert.NotNil(t, taskRes)
assert.Equal(t, "test", taskRes.Task)
assert.False(t, false, taskRes.Done)
assert.Equal(t, time, taskRes.Deadline)
```

実際にテストをしてみました。

成功した場合は下のように表示されます。
```zsh:zsh
❯  go test -v ./...
=== RUN   TestCreate
2024/10/16 19:26:45 deadline 2024-10-11 00:00:00 +0000 UTC
--- PASS: TestCreate (0.00s)
PASS
ok      github.com/maooz4426/Todolist/usecases/interactor       0.251s
```

失敗した場合を確認するためにしたようなコードを追加してみました。

```go:go
assert.NoError(t, err)
assert.NotNil(t, taskRes)
assert.Equal(t, "a", taskRes.Task)
log.Println("failしたよ〜ん") //追加
assert.False(t, false, taskRes.Done)
assert.Equal(t, time, taskRes.Deadline)
```

実際にテストを実行すると下のようになります。

```zsh:zsh
❯  go test -v ./...
=== RUN   TestCreate
    todo_test.go:39:
                Error Trace:    /Users/maoz/Documents/Go/todo-go-clean/usecases/interactor/todo_test.go:39
                Error:          Not equal:
                                expected: "a"
                                actual  : "test"

                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -a
                                +test
                Test:           TestCreate
2024/10/16 19:34:27 failしたよ〜ん
--- FAIL: TestCreate (0.00s)
FAIL
```

失敗しても実行が継続されています。

# requireを使ってみる

requireは上のコードを使って、パッケージだけ変えて使ってみようと思います。
```go:go
require.NoError(t, err)
require.NotNil(t, taskRes)
require.Equal(t, "a", taskRes.Task)
log.Println("failしたよ〜ん")
require.False(t, false, taskRes.Done)
require.Equal(t, time, taskRes.Deadline)
```

requireではassertと違い、テストを失敗すると、そこで実行が終了します。

```zsh:zsh
❯  go test -v ./...
=== RUN   TestCreate
    todo_test.go:39:
                Error Trace:    /Users/maoz/Documents/Go/todo-go-clean/usecases/interactor/todo_test.go:39
                Error:          Not equal:
                                expected: "a"
                                actual  : "test"

                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -a
                                +test
                Test:           TestCreate
--- FAIL: TestCreate (0.00s)

```

# まとめ
tesitifyパッケージを使うと楽にテストを書くことができるのに加え、多くのメソッドがあるためコードを読みやすく書くこともできると感じました。

ぜひテストケースを書くときは使ってみつつ、やりたいことを抽象化したメソッドがないか調べてみてください

https://pkg.go.dev/github.com/stretchr/testify/assert

# Go関連の記事
https://qiita.com/maooz4426/items/592028f297ca76830c6b

https://qiita.com/maooz4426/items/63071aba6a69c9a55ef4

https://qiita.com/maooz4426/items/e1af6b5994e0c6c067d7

https://qiita.com/maooz4426/items/e1933175e492883db26d

https://qiita.com/maooz4426/items/91fa49341bc2e9d8eddd

https://qiita.com/maooz4426/items/2f9fe5e8d6b6a44b3d57