Golang用于测试的接口

huangapple go评论91阅读模式
英文:

golang interfaces for testing

问题

我正在尝试在我的代码中创建一个数据库模拟,然后我正在为代码引入接口,以创建模拟:

这是我的代码(我不知道这是否是正确的方法)

package interfaces

type ObjectAPI interface {
	FindSomethingInDatabase(ctx context.Context, name string) (e.Response, error)
}

我的接口实现如下:

package repositories

func FindSomethingInDatabase(ctx context.Context, name string) (e.Response, error) {

	statement, err := db.SqlStatementWithCtx(ctx,
		`SELECT * 
		 FROM table
		 WHERE name = ? LIMIT 1`)

	row := statement.QueryRowContext(ctx, name)

	if err != nil {
		return e.Response{}, err
	}

	statement.Close()
	return toResponse(row), nil  //这个方法将数据库行转换为e.Response结构体

}

现在,我需要从一个方法中调用FindSomethingInDatabase的实现,然后我会接收一个对象类型的接口:

func CallImplementation(request *dto.XRequest, repo i.ObjectAPI) dto.XResponse{
	result := repo.FindSomethingInDatabase(request.Ctx, request.Name)
	// 更多代码
}

但是现在我不知道如何调用CallImplementation来传递一个带有实现的对象。

调用该方法时传递接口的实现。

英文:

I am trying to create a database mock in my code, then i am introducing interfaces to my code, to create the mock:

This is my code (I don't know if it's the correct approach)

package interfaces

type ObjectAPI interface {
	FindSomethingInDatabase(ctx context.Context, name string) (e.Response, error)
}

And my implementation of the interface is:

package repositories

func FindSomethingInDatabase(ctx context.Context, name string) (e.Response, error) {

	statement, err := db.SqlStatementWithCtx(ctx,
		`SELECT * 
		 FROM table
		 WHERE name = ? LIMIT 1`)

	row := statement.QueryRowContext(ctx, name)

	if err != nil {
		return e.Response{}, err
	}

	statement.Close()
	return toResponse(row), nil  //this method convert row database to e.Response struct

}

Now I need call from one method the implementation of my FindSomethingInDatabase, then i am receiving an object type interface:

func CallImplementation(request *dto.XRequest, repo i.ObjectAPI) dto.XResponse{
	result := repo.FindSomethingInDatabase(request.Ctx, request.Name)
// more code
}

But now I don't know how can I call CallImplementation` to pass an object with the implementation.

Call the method passing the implementation of the interface

答案1

得分: 0

一个接口描述了一个类型。由于你的FindSomethingInDatabase实现只是一个没有接收者的函数,所以没有类型实现了ObjectAPI接口。

你可以将类型为func(ctx context.Context, name string) (e.Response, error)的值作为回调传递给CallImplementation,并完全摒弃接口。或者保留接口,定义一个类型,并将该类型作为当前FindSomethingInDatabase实现的接收者。然后你可以将该类型传递给CallImplementation,因为它现在实现了ObjectAPI接口。以下是后一种方法的示例(这是我首选的可扩展性选项):

type database struct {}

func (d *database) FindSomethingInDatabase(ctx context.Context, name string) (e.Response, error) {
    // ...
}

func CallImplementation(request *dto.XRequest, repo i.ObjectAPI) dto.XResponse{
    result := repo.FindSomethingInDatabase(request.Ctx, request.Name)
// more code
}

func main() {
    db := &database{}
    _ = Callimplementation(getRequest(), db)
}

在这种情况下,你可能希望将db作为database的成员变量存储,而不是作为全局变量(目前似乎是这种情况)。

英文:

An interface describes a type. Since your FindSomethingInDatabase implementation is just a func without a receiver, there is no type that implements interface ObjectAPI.

You can pass a value of type func(ctx context.Context, name string) (e.Response, error) into CallImplementation as a callback and get rid of the interface altogether. Alternatively, keep the interface, define a type, and make that type the receiver for your current FindSomethingInDatabase implementation. You can then pass the type to CallImplementation, since it will now implement the ObjectAPI interface. An example of the latter (which would be my preferred option for extensibility):

type database struct {}

func (d *database) FindSomethingInDatabase(ctx context.Context, name string) (e.Response, error) {
    // ...
}

func CallImplementation(request *dto.XRequest, repo i.ObjectAPI) dto.XResponse{
    result := repo.FindSomethingInDatabase(request.Ctx, request.Name)
// more code
}

func main() {
    db := &database{}
    _ = Callimplementation(getRequest(), db)
}

In this case, you will probably want to store db as a member of database, rather than having it as a global variable (which appears to be the case now).

答案2

得分: 0

参考mockery。它提供了接口的自动生成模拟对象的功能,并可以作为模拟的最佳实践的参考。

通常你会这样做:

api.go:

type API interface {
    Something(ctx context.Context, name string) (e.Response, error)
}

type ApiImpl struct {
  
}

func (t *ApiImpl) Something(ctx context.Context, name string) (e.Response, error) {
    // 实现
}

api_test.go

// 模拟对象可以手动编写,也可以使用mockery自动生成
type MockImpl struct {
}

func (m *MockImpl) Something(ctx context.Context, name string) (e.Response, error) {
    // 模拟实现
}

func TestSomething(t * testing.T) {
    // 使用模拟对象的测试代码
}
英文:

refer mockery. it provides auto generation of mocks for interfaces and can be good reference about best practices for mocking.

typically you would do this:

api.go:

type API interface {
    Something(ctx context.Context, name string) (e.Response, error)
}

type ApiImpl struct {
  
}

func (t *ApiImpl) Something(ctx context.Context, name string) (e.Response, error) {
    // impl
}

api_test.go

// mocks can be hand coded or autogenerated using mockery
type MockImpl struct {
}

func (m *MockImpl) Something(ctx context.Context, name string) (e.Response, error) {
    // mock implementation
}

func TestSomething(t * testing.T) {
    // test code with mocks
}

huangapple
  • 本文由 发表于 2023年2月8日 03:45:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75378279.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定