コンテンツにスキップ

Goのテストツール@アプリのホワイトボックステスト

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. ホワイトボックステストのツール

整形

ツール名 解析内容 理由
標準のgo fmtコマンド


静的解析

▼ ベストプラクティス

ツール名 解析内容 理由
標準のgo vetコマンド
gochecknoinits init関数を使用していないかを検証する。 init関数を使うと、プログラムの挙動が複雑になり、可読性が低下するため、使用しするべきではない。
gomnd マジックナンバーを採用していないかを検証する。
funlen メソッド名が指定した文字数より長くないかを検証する。
lll 一行が指定した文字数より長くないかを検証する。
statickcheck パフォーマンスの出る実装方法になっているかを検証する。
stylecheck Effective Goに則った方法で実装しているかを検証する。 パッケージ名にアンダースコアを使用していないか。キャメルケースの変数名に略語 (例:HTTP、IDなど) を使う場合、略語は全て大文字にする。
whitespace 不要な改行がないかを検証する。
gocognit コードが複雑 (例:ifの入れ子) であるかどうかを検証する。
godox TODOコメントがあるかどうかを検証する。
goimports 足りないimportや余分なimportがあるかどうかを検証する。
goprintffuncname printfのようなメソッドの名前がfで終わっているかを検証する。
revive 用意されたコード規約に則っているかを検証する。

▼ 脆弱性

ツール名 解析内容 理由
govulncheck
gosec

▼ コード規約違反

ユーザー定義のコード規約違反を検証する。


ユニットテスト、機能テストツール

  • 標準のgo fmtコマンド


02. 標準のテストツール

標準のテストツールとは

goコマンドが提供するホワイトボックス機能のこと。


網羅率

網羅率はパッケージを単位として解析される。


03. 設計規約

パッケージ名

▼ ホワイトボックステスト

テストファイルのパッケージ名が、同じディレクトリ配下にある実際の処理ファイルのパッケージ名と同じ場合、それはホワイトボックステストになる。

▼ ブラックボックス風のホワイトテスト

テストファイルのパッケージ名が、同じディレクトリ配下にある実際の処理ファイルに『_test』を加えたパッケージ名の場合、それはブラックボックステスト風のホワイトテストになる。

補足として、Goでは1つのディレクトリ内に1つのパッケージ名しか宣言できないが、ブラックボックステストのために『_test』を加えることは許されている。


インターフェースの導入

テストできない構造体はモックに差し替えられることなる。

この時、あらかじめ実際の構造体をインターフェースの実装にしておく。

テスト時に、モックもインターフェイスの実装とすれば、モックが実際の構造体と同じデータ型として認識されるようになる。

これにより、モックに差し替えられるようになる。


テーブル駆動テスト

テストデータ (datain) と期待値 (expectedwant) をファイル (例:jsonyaml) として用意しておく。

これをReadFile関数で読み出し、テストケースの構造体を定義する。

テストケースの構造体を反復処理し、テストを実施する。

package test

import (
    "io/ioutil"
    "testing"
)

/**
 * foo関数をテストする
 */
func TestFoo(t *testing.T) {

    // ファイルを読み込む。
    expected_foo_succeed_status, _ := ioutil.ReadFile("../testdata/expected/foo_succeed_status.json")
    data_foo_succeed_status, _ := ioutil.ReadFile("../testdata/data/foo_succeed_status.json")

    expected_foo_failed_status, _ := ioutil.ReadFile("../testdata/expected/foo_succeed_status.json")
    data_foo_failed_status, _ := ioutil.ReadFile("../testdata/data/foo_succeed_status.json")

    // テストケース
    cases := []struct {
        // テストケース名
        name string
        // 期待値
        expected string
        // テストデータ
        data []byte
    }{
        {
            name:     "TestFoo_SucceedStatus_ReturnOk",
            expected: expected_foo_succeed_status,
            data:     data_foo_succeed_status,
        },
        {
            name:     "TestFoo_FailedStatus_ReturnOk",
            expected: expected_foo_failed_status,
            data:     data_foo_failed_status,
        },
    }

    // テストケースを反復で処理する。
    for _, tt := range cases {
        t.Run(tt.name, func(t *testing.T) {

            // foo関数を実行し、実際値を作成する。

            // 期待値と実際値を比較する。
            assert.JSONEq(t, tt.expected, actual)
        })
    }
}