Golang中如何解决测试代码拆分到多个文件的问题

Golang中如何解决测试代码拆分到多个文件的问题 我为我的API编写了大量的测试,这些测试都放在一个文件中,并且一切运行正常。现在我想将这些测试分离到单独的文件中,但无论我尝试什么方法,正如你在代码仓库中看到的,我使用了处理程序模块,因此无法将测试放在处理程序模块中,因为存在循环依赖。此外,为了使测试通过,必须连接到数据库,因此所有函数都必须建立数据库连接。

将测试移动到单独的文件夹中,我也未能成功。

让我们考虑一个最简单的例子:这是我的 server_test.go 文件。

package main

var (
	client        *mongo.Client
	appDB         db.AppRepository
	mongoDatabase *mongo.Database
	configDB      connstring.ConnString
	s3Endpoint    string
)

func TestMain(m *testing.M) {
	// Set up resources before running tests
	setup()
	// Run the tests
	code := m.Run()
	teardown()
	os.Exit(code)
}

func copyFile(src, dst string) {
	input, err := os.ReadFile(src)
	if err != nil {
		logrus.Errorf("Failed to read the file: %v", err)
		return
	}

	err = os.WriteFile(dst, input, 0644)
	if err != nil {
		logrus.Errorf("Failed to copy the file: %v", err)
		return
	}
}

func removeFile(filename string) {
	err := os.Remove(filename)
	if err != nil {
		logrus.Errorf("Failed to remove the file: %v", err)
		return
	}
}

func setup() {
	viper.SetConfigType("env")
	viper.SetConfigName(".env")
	// set the configuration file path
	viper.AddConfigPath(".")
	// read in the configuration file
	if err := viper.ReadInConfig(); err != nil {
		panic(err)
	}
	// Create a single database connection
	flagMap := map[string]interface{}{
		"migration":     true,
		"rollback":      false,
		"user_name":     "admin",
		"user_password": "password",
	}
	s3Endpoint = viper.GetString("S3_ENDPOINT")
	client, configDB = mongod.ConnectToDatabase(viper.GetString("MONGODB_URL_TESTS"), flagMap)
	appDB = mongod.NewAppRepository(&configDB, client)
	mongoDatabase = client.Database(configDB.Database)
	copyFile("LICENSE", "testapp.dmg")
	copyFile("LICENSE", "testapp.pkg")

}

func teardown() {
	adminsCollection := mongoDatabase.Collection("admins")
	filter := bson.M{"username": "admin"}

	// Delete the admin user from the collection
	_, err := adminsCollection.DeleteOne(context.TODO(), filter)
	if err != nil {
		logrus.Errorf("Failed to remove admin user: %v", err)
	}
	log.Println("Successfully removed admin user.")
	client.Disconnect(context.Background())
	log.Println("MongoDB is disconnected.")
	removeFile("testapp.dmg")
	removeFile("testapp.pkg")
}

func TestHealthCheck(t *testing.T) {

	router := gin.Default()
	w := httptest.NewRecorder()

	handler := handler.NewAppHandler(client, appDB, mongoDatabase)
	router.GET("/health", func(c *gin.Context) {
		handler.HealthCheck(c)
	})

	req, _ := http.NewRequest("GET", "/health", nil)

	// Serve the request using the Gin router.
	router.ServeHTTP(w, req)

	// Check the response status code.
	assert.Equal(t, http.StatusOK, w.Code)

	// Check the response body.
	expected := `{"status":"healthy"}`
	assert.Equal(t, expected, w.Body.String())
}

如何正确地将 TestHealthCheck 函数移动到 tests/info_test.go 中?当然,需要将数据库连接参数传递给它……


更多关于Golang中如何解决测试代码拆分到多个文件的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何解决测试代码拆分到多个文件的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中解决测试代码拆分到多个文件的问题,可以通过以下几种方式实现:

方案1:使用测试辅助包(推荐)

创建一个 testhelper 包来管理共享的测试资源:

// testhelper/setup.go
package testhelper

import (
    "context"
    "log"
    "os"
    "testing"

    "github.com/sirupsen/logrus"
    "github.com/spf13/viper"
    "go.mongodb.org/mongo-driver/mongo"
    "your-project/db"
    "your-project/connstring"
    "your-project/mongod"
)

var (
    Client        *mongo.Client
    AppDB         db.AppRepository
    MongoDatabase *mongo.Database
    ConfigDB      connstring.ConnString
    S3Endpoint    string
)

func Setup() {
    viper.SetConfigType("env")
    viper.SetConfigName(".env")
    viper.AddConfigPath(".")
    
    if err := viper.ReadInConfig(); err != nil {
        panic(err)
    }
    
    flagMap := map[string]interface{}{
        "migration":     true,
        "rollback":      false,
        "user_name":     "admin",
        "user_password": "password",
    }
    
    S3Endpoint = viper.GetString("S3_ENDPOINT")
    Client, ConfigDB = mongod.ConnectToDatabase(viper.GetString("MONGODB_URL_TESTS"), flagMap)
    AppDB = mongod.NewAppRepository(&ConfigDB, Client)
    MongoDatabase = Client.Database(ConfigDB.Database)
    
    copyFile("LICENSE", "testapp.dmg")
    copyFile("LICENSE", "testapp.pkg")
}

func Teardown() {
    adminsCollection := MongoDatabase.Collection("admins")
    filter := bson.M{"username": "admin"}
    
    _, err := adminsCollection.DeleteOne(context.TODO(), filter)
    if err != nil {
        logrus.Errorf("Failed to remove admin user: %v", err)
    }
    
    Client.Disconnect(context.Background())
    removeFile("testapp.dmg")
    removeFile("testapp.pkg")
}

func copyFile(src, dst string) {
    input, err := os.ReadFile(src)
    if err != nil {
        logrus.Errorf("Failed to read the file: %v", err)
        return
    }
    
    err = os.WriteFile(dst, input, 0644)
    if err != nil {
        logrus.Errorf("Failed to copy the file: %v", err)
        return
    }
}

func removeFile(filename string) {
    err := os.Remove(filename)
    if err != nil {
        logrus.Errorf("Failed to remove the file: %v", err)
        return
    }
}

方案2:使用 _test 包和全局变量

在单独的测试文件中使用 _test 包:

// tests/info_test.go
package main_test

import (
    "context"
    "net/http"
    "net/http/httptest"
    "testing"
    
    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
    "go.mongodb.org/mongo-driver/mongo"
    "your-project/db"
    "your-project/handler"
)

// 导出测试辅助函数
func GetTestClient() *mongo.Client {
    return client
}

func GetTestAppDB() db.AppRepository {
    return appDB
}

func GetTestMongoDatabase() *mongo.Database {
    return mongoDatabase
}

func TestHealthCheck(t *testing.T) {
    router := gin.Default()
    w := httptest.NewRecorder()
    
    handler := handler.NewAppHandler(GetTestClient(), GetTestAppDB(), GetTestMongoDatabase())
    router.GET("/health", func(c *gin.Context) {
        handler.HealthCheck(c)
    })
    
    req, _ := http.NewRequest("GET", "/health", nil)
    router.ServeHTTP(w, req)
    
    assert.Equal(t, http.StatusOK, w.Code)
    expected := `{"status":"healthy"}`
    assert.Equal(t, expected, w.Body.String())
}

方案3:使用测试套件结构体

创建一个测试套件结构体来封装测试依赖:

// tests/suite_test.go
package tests

import (
    "context"
    "os"
    "testing"
    
    "github.com/sirupsen/logrus"
    "github.com/spf13/viper"
    "go.mongodb.org/mongo-driver/mongo"
    "your-project/db"
    "your-project/connstring"
    "your-project/mongod"
)

type TestSuite struct {
    Client        *mongo.Client
    AppDB         db.AppRepository
    MongoDatabase *mongo.Database
    ConfigDB      connstring.ConnString
    S3Endpoint    string
    t             *testing.T
}

func NewTestSuite(t *testing.T) *TestSuite {
    ts := &TestSuite{t: t}
    ts.Setup()
    return ts
}

func (ts *TestSuite) Setup() {
    viper.SetConfigType("env")
    viper.SetConfigName(".env")
    viper.AddConfigPath(".")
    
    if err := viper.ReadInConfig(); err != nil {
        ts.t.Fatal(err)
    }
    
    flagMap := map[string]interface{}{
        "migration":     true,
        "rollback":      false,
        "user_name":     "admin",
        "user_password": "password",
    }
    
    ts.S3Endpoint = viper.GetString("S3_ENDPOINT")
    ts.Client, ts.ConfigDB = mongod.ConnectToDatabase(viper.GetString("MONGODB_URL_TESTS"), flagMap)
    ts.AppDB = mongod.NewAppRepository(&ts.ConfigDB, ts.Client)
    ts.MongoDatabase = ts.Client.Database(ts.ConfigDB.Database)
    
    ts.copyFile("LICENSE", "testapp.dmg")
    ts.copyFile("LICENSE", "testapp.pkg")
}

func (ts *TestSuite) Teardown() {
    adminsCollection := ts.MongoDatabase.Collection("admins")
    filter := bson.M{"username": "admin"}
    
    _, err := adminsCollection.DeleteOne(context.TODO(), filter)
    if err != nil {
        logrus.Errorf("Failed to remove admin user: %v", err)
    }
    
    ts.Client.Disconnect(context.Background())
    ts.removeFile("testapp.dmg")
    ts.removeFile("testapp.pkg")
}

func (ts *TestSuite) copyFile(src, dst string) {
    input, err := os.ReadFile(src)
    if err != nil {
        logrus.Errorf("Failed to read the file: %v", err)
        return
    }
    
    err = os.WriteFile(dst, input, 0644)
    if err != nil {
        logrus.Errorf("Failed to copy the file: %v", err)
        return
    }
}

func (ts *TestSuite) removeFile(filename string) {
    err := os.Remove(filename)
    if err != nil {
        logrus.Errorf("Failed to remove the file: %v", err)
        return
    }
}
// tests/info_test.go
package tests

import (
    "net/http"
    "net/http/httptest"
    "testing"
    
    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
    "your-project/handler"
)

func TestHealthCheck(t *testing.T) {
    ts := NewTestSuite(t)
    defer ts.Teardown()
    
    router := gin.Default()
    w := httptest.NewRecorder()
    
    handler := handler.NewAppHandler(ts.Client, ts.AppDB, ts.MongoDatabase)
    router.GET("/health", func(c *gin.Context) {
        handler.HealthCheck(c)
    })
    
    req, _ := http.NewRequest("GET", "/health", nil)
    router.ServeHTTP(w, req)
    
    assert.Equal(t, http.StatusOK, w.Code)
    expected := `{"status":"healthy"}`
    assert.Equal(t, expected, w.Body.String())
}

方案4:使用 init() 函数和包级变量

在单独的测试文件中使用 init() 函数:

// tests/info_test.go
package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
    
    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
    "your-project/handler"
)

func init() {
    // 确保 setup 已经运行
    if client == nil {
        setup()
    }
}

func TestHealthCheck(t *testing.T) {
    router := gin.Default()
    w := httptest.NewRecorder()
    
    handler := handler.NewAppHandler(client, appDB, mongoDatabase)
    router.GET("/health", func(c *gin.Context) {
        handler.HealthCheck(c)
    })
    
    req, _ := http.NewRequest("GET", "/health", nil)
    router.ServeHTTP(w, req)
    
    assert.Equal(t, http.StatusOK, w.Code)
    expected := `{"status":"healthy"}`
    assert.Equal(t, expected, w.Body.String())
}

对于你的具体情况,推荐使用方案1或方案3。方案1通过创建独立的测试辅助包来避免循环依赖,方案3通过测试套件结构体提供了更好的封装和灵活性。这两种方案都能让你将测试代码拆分到多个文件中,同时共享数据库连接和其他测试资源。

回到顶部