Open3

学習記録

kokikoki

【Go言語】SOLID原則

  • Robert C Martinが考案したSOLIDは、オブジェクト思考において良いクラスの設計手法をまとめた5つの原理原則です。各原則の頭文字を1文字ずつ取っています。

【S】単一責任の原則:Single Responsibility Principle

  • 1つのクラスは1つの責任を持つべきで、1つしか持ってはいけない。

【O】Open/Closed Principle:オープン・クローズドの原則

  • コードを修正することなく、振る舞いを変更できるようにするべきである。

【L】Liskov Substitution Principle:リスコフの置換原則

  • 派生型は、基底型を代替可能としなければならない。

【I】Interface Segregation Principle:インタフェース分離の原則

  • クライアントが限定された複数のインタフェースは、汎用的な1つのインタフェースよりも優れている。

【D】Dependency Inversion Principle:依存関係逆転の原則

  • 抽象に対して依存するべきであり、具象に対して依存してはならない。
  • Go言語においては、次の2つに解釈できる。
    1. 全てのパッケージはインタフェースを持つべきで、具体的な細部を知らなくても機能概要を把握できるようにすべきである。
    2. パッケージに依存関係が必要となった場合、パラメータとして依存関係を受け入れるべきである。

まとめ

S:クラスは1つのみ責務を持つよう分割し、

O:コードを修正せずに拡張できるよう構成し、

L:基底クラスは抽象クラスで代替できるよう設計し、

I:インタフェースは1つにまとめずクライアントごとに分割し、

D:依存関係は実装ではなくインタフェースに限定することである。

参考記事

kokikoki

📚 import文の詳細解説

  1. Go標準パッケージ

"context"
何のため?: APIメソッドにcontextを渡すため
実際の使用例:

result, err := repo.GetCheckStatusSummary(context.Background(), userID,
 checkID, "")
//                                      ↑ここで使う
なぜ必要?: GoのAPIでは必ずcontextを渡すのが慣例(タイムアウト管理やキ
ャンセル処理のため)

"testing"
何のため?: Go標準のテストフレームワーク
実際の使用例:

func TestGetCheckStatusSummary_WithCompletedStatus(t *testing.T) {
//                                                ↑ *testing.T型
    if err != nil {
        t.Errorf("unexpected error: %v", err) // ← t.Errorf()で使う
    }
}

"time"
何のため?: 日付・時刻の操作
実際の使用例:

testTime := time.Date(2025, 9, 3, 10, 0, 0, 0, time.Local)
//           ↑ 特定の日時を作成
assert.WithinDuration(t, expectedTime, actualTime, time.Second)
//                                                ↑ 誤差許容範囲
  1. データベース関連

_ "github.com/go-sql-driver/mysql"
_って何?: 「ブランクインポート」= パッケージ名を使わないimport
何のため?: MySQLドライバーの初期化のみ
裏で何が起こる?:

// このドライバーのinit()関数が実行されて、
// database/sqlにMySQLドライバーが登録される
func init() {
    sql.Register("mysql", &MySQLDriver{})
}

実際には: 直接使わないが、sqlx.Open("mysql", ...)で使われる

"github.com/jmoiron/sqlx"
何のため?: database/sqlの便利な拡張版
普通のsqlパッケージとの違い:

// 標準のsql
rows, err := db.Query("SELECT * FROM users WHERE id = ?", 1)
// 手動でScan...

// sqlxなら
var user User
err := db.Get(&user, "SELECT * FROM users WHERE id = ?", 1)
// 構造体に自動マッピング!
  1. テスト支援ライブラリ

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
違いは?:

// assert: 失敗してもテスト継続
assert.Equal(t, expected, actual) // 失敗してもここで止まらない
fmt.Println("この行も実行される")

// require: 失敗したらテスト中止
require.Equal(t, expected, actual) // 失敗したらここで止まる
fmt.Println("この行は実行されない")

使い分け:

  • require: 必須チェック(DB接続失敗など、続行不可能)
  • assert: 期待値チェック(続行可能)
  1. プロジェクト固有

"github.com/triple-three-inc/product-template-backend/infrastructure/ha
ck"
何のため?: テスト用のDB接続文字列を取得
実際の使用例:

db := sqlx.MustOpen("mysql", hack.GetDBConnectionString())
//                           ↑ 
"root:password@tcp(localhost:13306)/triple_report_test?parseTime=true"
//                             みたいな文字列を返す
なぜhackパッケージ?: テスト用の便利機能をまとめた場所

💡 なぜこれらすべてが必要?

  func TestExample(t *testing.T) {
  //               ↑ testing パッケージ
      db := sqlx.MustOpen("mysql", hack.GetDBConnectionString())
  //        ↑ sqlx      ↑ mysql driver    ↑ hack
      defer db.Close()

      testTime := time.Date(2025, 9, 3, 10, 0, 0, 0, time.Local)
  //              ↑ time パッケージ

      result, err := repo.GetCheckStatusSummary(context.Background(),
  ...)
  //                                           ↑ context パッケージ

      require.NoError(t, err)        // ← require(必須チェック)
      assert.Equal(t, expected, result) // ← assert(期待値チェック)
  }
kokikoki

🔧 引数の詳細解説

関数のシグネチャ(署名)

  func setupTestData(t *testing.T, tx *sqlx.Tx) *TestData
  //   関数名      引数1       引数2      戻り値の型

引数1: t *testing.T

t *testing.T // tという名前の、*testing.T型のポインタ

何?: Goのテストフレームワークが提供するテスト制御オブジェクト
どこから来る?: 各テスト関数が受け取って、それを渡す

func TestSomething(t *testing.T) {  // ← Goが自動で渡してくれる
    setupTestData(t, tx)  // ← それを更にヘルパーに渡す
}

何ができる?:

t.Errorf("エラー: %v", err)    // エラー報告
t.Fatalf("致命的エラー: %v", err) // テスト中止
t.Log("デバッグ情報")           // ログ出力
t.Helper()                   // ヘルパー宣言

引数2: tx *sqlx.Tx

tx *sqlx.Tx // txという名前の、*sqlx.Tx型のポインタ

何?: データベーストランザクションオブジェクト
なぜ必要?: テストデータをデータベースに挿入するため
どこから来る?: 呼び出し元のテスト関数で作成して渡す

  func TestSomething(t *testing.T) {
      // 1. DB接続
      db := sqlx.MustOpen("mysql", hack.GetDBConnectionString())
      defer db.Close()

      // 2. トランザクション開始
      tx, err := db.Beginx()
      if err != nil {
          t.Fatalf("トランザクション開始失敗: %v", err)
      }
      defer tx.Rollback()  // テスト終了時に自動でロールバック

      // 3. ヘルパーに渡す
      data := setupTestData(t, tx)  // ← ここで渡される
  }

トランザクションを使う理由:

tx.Exec("INSERT INTO groups ...")    // テストデータ作成
tx.Exec("INSERT INTO audits ...")    // テストデータ作成
// テスト実行
tx.Rollback()  // 全部なかったことに!(自動クリーンアップ)

戻り値: *TestData

*TestData // TestData構造体のポインタ

なぜポインタ?:

  • 構造体のコピーではなく、同じメモリ領域を参照
  • 効率的で、変更も反映される

🎯 実際の使用例

  func TestExample(t *testing.T) {
      // DB接続とトランザクション準備
      db := sqlx.MustOpen("mysql", hack.GetDBConnectionString())
      defer db.Close()
      tx, _ := db.Beginx()
      defer tx.Rollback()

      // ヘルパー関数呼び出し
      data := setupTestData(t, tx)
      //       ↑t: テスト制御  ↑tx: DB操作  ↑戻り値: テストデータ情報

      // テスト実行
      repo := NewCheckRepository(tx)
      result, err := repo.GetCheckStatusSummary(..., data.CheckID, ...)
  }