如何创建可模拟的代码而不会遮蔽用法(最好使用接口)

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

How to create mockable code without shadowing usages (preferably with interfaces)

问题

我已经为你翻译了以下内容:

我设计了我的系统,所以一个层次接收一个较低层次的接口。这似乎是在golang中创建可模拟代码的最佳实践方式。较高级别的层次可以接受任何实现该接口的结构体,因此您可以使用真实的较低层次或模拟的较低层次调用较高层次。问题是较低层次的用法丢失了。由于抽象,编译器无法看到较低层次的使用位置。这种可见性在重构时尤为重要,因此程序员可以看到函数在哪里使用--而不依赖于控制-f。我已经包含了当前架构的最小化版本,如果您将代码复制到IDE中,您可以通过尝试查找Get() > Repository > repository.go的所有用法来查看问题。

如何在使用接口的情况下使此模式工作,而不会隐藏较低层次的用法?

英文:

I've designed my system, so a layer receives an interface for a lower layer. This seems to be the best practice way to create mockable code in golang. The higher level layer can accept any struct that implements the interface, so you can call the higher layer with a real lower layer or a mocked lower layer. The problem is that the lower layers usages are lost. Because of the abstraction, the compiler can't see where the lower layer is used. This visibility is especially important when refactoring, so the programmer can see everywhere a function is used--without relying on control-f. I've included a minimized version of the current architecture, if you were to copy the code into an ide, you could see the issue by attempting to find all usages of Get() > Repository > repository.go

How can I make this pattern work, using interfaces, without shadowing the usages of lower layers?

Package - main

File - main.go

package main

import (
	"awesomeProject1/internal"
	"fmt"
)

func main() {
	realRepo := &internal.Repository{}
	realService := internal.Service{Repo: realRepo}
	fmt.Println(realService.FindById(1))
}

Package - Internal

File - service.go

package internal

type Service struct {
	Repo IRepository
}

type IRepository interface {
	Get(id uint64) string
}

func (service *Service) FindById(id uint64) string {
	return service.Repo.Get(id)
}

File - repository.go

package internal

type Repository struct {
}

func (repo *Repository) Get(id uint64) string {
	return "a real value from db"
}

Package - tests

File - service_test.go

package tests

import (
	"awesomeProject1/internal"
	"fmt"
	"testing"
)

func TestService(t *testing.T) {
	mockRepo := &MockRepository{}
	realService := internal.Service{Repo: mockRepo}
	fmt.Println(realService.FindById(1))
}

File - mock_repository.go

package tests

type MockRepository struct {
}

func (repo *MockRepository) Get(id uint64) string {
	return "a fake value for testing"
}

答案1

得分: 0

你可以使用golang中的mockery工具来实现。

  1. 首先安装mockery工具(链接:https://github.com/vektra/mockery)。

PS C:\GolandProjects\Stackoverflow\internal> go install github.com/vektra/mockery/v2@latest

  1. 在终端中进入要生成模拟文件的接口所在的文件夹,并生成模拟文件。

PS C:\GolandProjects\Stackoverflow\internal> mockery --name IRepository
22 Sep 22 13:45 IST INF Starting mockery dry-run=false version=v2.14.0
22 Sep 22 13:45 IST INF Walking dry-run=false version=v2.14.0
22 Sep 22 13:45 IST INF Generating mock dry-run=false interface=IRepository qualified-name=awesomeProject1/internal version=v2.14.0

  1. 你将会看到一个名为"mocks"的文件夹和一个与接口名称相同的模拟文件。

  2. 模拟IRepository.go文件。

// Code generated by mockery v2.14.0. DO NOT EDIT.

package mocks

import mock "github.com/stretchr/testify/mock"

// IRepository is an autogenerated mock type for the IRepository type
type IRepository struct {
	mock.Mock
}

// Get provides a mock function with given fields: id
func (_m *IRepository) Get(id uint64) string {
	ret := _m.Called(id)

	var r0 string
	if rf, ok := ret.Get(0).(func(uint64) string); ok {
		r0 = rf(id)
	} else {
		r0 = ret.Get(0).(string)
	}

	return r0
}

type mockConstructorTestingTNewIRepository interface {
	mock.TestingT
	Cleanup(func())
}

// NewIRepository creates a new instance of IRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewIRepository(t mockConstructorTestingTNewIRepository) *IRepository {
	mock := &IRepository{}
	mock.Mock.Test(t)

	t.Cleanup(func() { mock.AssertExpectations(t) })

	return mock
}
  1. 在下面的文件中,你需要为IRepository创建对象。
package tests

import (
	"awesomeProject1/internal"
	"fmt"
	"testing"
)

func TestService(t *testing.T) {

	mockRepo := mocks.IRepository{}
	mockRepo.On("Get", mock.Anything).Return("some string")

	realService := internal.Service{Repo: &mockRepo}
	fmt.Println(realService.FindById(1))
}
英文:

You can do by using the mockery tool in golang

  1. First install the mockery tool (link: https://github.com/vektra/mockery)

PS C:\GolandProjects\Stackoverflow\internal> go install github.com/vektra/mockery/v2@latest

  1. Go to the folder location in terminal for which interface u want to generate the mock file and generate the mock

    PS C:\GolandProjects\Stackoverflow\internal> mockery --name IRepository
    22 Sep 22 13:45 IST INF Starting mockery dry-run=false version=v2.14.0
    22 Sep 22 13:45 IST INF Walking dry-run=false version=v2.14.0
    22 Sep 22 13:45 IST INF Generating mock dry-run=false interface=IRepository qualified-    
              name=awesomeProject1/internal version=v2.14.0
    
  2. You will see one folder called mocks and the mock file with interface name

  3. Mock IRepository.go

     // Code generated by mockery v2.14.0. DO NOT EDIT.
    
     package mocks
    
     import mock "github.com/stretchr/testify/mock"
    
     // IRepository is an autogenerated mock type for the IRepository type
     type IRepository struct {
     	mock.Mock
     }
    
     // Get provides a mock function with given fields: id
     func (_m *IRepository) Get(id uint64) string {
     	ret := _m.Called(id)
    
     	var r0 string
     	if rf, ok := ret.Get(0).(func(uint64) string); ok {
     		r0 = rf(id)
     	} else {
     		r0 = ret.Get(0).(string)
     	}
    
     	return r0
     }
    
     type mockConstructorTestingTNewIRepository interface {
     	mock.TestingT
     	Cleanup(func())
     }
    
     // NewIRepository creates a new instance of IRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
     func NewIRepository(t mockConstructorTestingTNewIRepository) *IRepository {
     	mock := &IRepository{}
     	mock.Mock.Test(t)
    
     	t.Cleanup(func() { mock.AssertExpectations(t) })
    
     	return mock
     }
    
  4. In the below file you need to create object for IRepository

    package tests
    
    import(
    "awesomeProject1/internal""fmt""testing"
    )
    
    funcTestService(t *testing.T){
    
    mockRepo := mocks.IRepository{}
    mockRepo.On("Get", mock.Anything).Return("some string")
    
    realService := internal.Service{Repo: &mockRepo}
    fmt.Println(realService.FindById(1))
    }

huangapple
  • 本文由 发表于 2022年9月22日 06:59:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/73807735.html
匿名

发表评论

匿名网友

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

确定