📝

go言語ArchUnitっぽいテストコードを作った

に公開

依存関係をUTで制御する良さそうなライブラリがなかったので、サクッと自作(AI作)したのでメモ。

import (
	"fmt"
	"golang.org/x/tools/go/packages"
	"strings"
	"testing"
)

type DependencyRule struct {
	Package         string   // パッケージ名
	ShouldNotDepend []string // 禁止される依存パッケージのリスト
}

func CollectDependencies(rules []DependencyRule) []error {
	var response []error

	cfg := &packages.Config{Mode: packages.NeedImports | packages.NeedDeps | packages.NeedName}
	// パッケージ全体の情報を取得
	pkgs, err := packages.Load(cfg, "./...")
	if err != nil {
		panic(err)
	}

	for _, pkg := range pkgs {
		for _, rule := range rules {
			targetMatch := strings.HasPrefix(pkg.PkgPath, rule.Package)
			// ルールにマッチしない場合はスキップ
			if !targetMatch {
				continue
			}
			for _, forbidden := range rule.ShouldNotDepend {
				for importPackage := range pkg.Imports {
					importMatch := strings.HasPrefix(importPackage, forbidden)
					if importMatch {
						response = append(response, fmt.Errorf("package %s depends on forbidden package %s", pkg.PkgPath, importPackage))
					}
				}
			}
		}
	}
	return response
}

func TestDependencyRules(t *testing.T) {
        // 規定のパッケージを指定
	prefix := "your-app/components/internal/"
        // 依存関係のルールを定義
	rules := []DependencyRule{
            // application層はadapter層に依存してはいけない
            {Package: prefix + "application", ShouldNotDepend: []string{
                prefix + "adapter",
            }},
            // domain層はadapter層, application層に依存してはいけない
            {Package: prefix + "domain", ShouldNotDepend: []string{
                prefix + "adapter",
                prefix + "application",
            }},
	}

	errors := CollectDependencies(rules)
	if len(errors) > 0 {
		var msg string
		for _, validate := range errors {
			msg += fmt.Sprintf("%v\n", validate)
		}
		t.Errorf("Dependency rule violated: \n%s", msg)
	}
}

Discussion