我在实现 Go 代码的接口替换时遇到一些问题。以前,代码编写时并没有考虑到单元测试,我将返回并添加对测试的支持(Testify 和 Mockery),并为我的代码库编写所有 Go 测试。
我当前尝试测试的函数之前收到了一个指向 Influx 包提供的 QueryTableResult 的指针结构。我已将其替换为本地界面:
type QueryTableResult interface {
Next() bool
Record() FluxRecord
Err() error
}
我的主要问题是 influx 包 QueryTableResult 结构提供的 Record() 返回一个特定的 FluxRecord 结构。我还为此结构添加了本地接口:
type FluxRecord interface {
Time() time.Time
Value() interface{}
}
并更改了 QueryTableResult 接口 Record() 方法以返回此接口而不是 influx 包中的特定结构。当我这样做时,我无法再执行我的流入查询函数,因为我收到此错误:
无法使用
(*api.QueryTableResult 类型的变量)作为 parseInfluxData 参数中的 QueryTableResult 值:*api.QueryTableResult 未实现 QueryTableResult(Record 方法的类型错误) 有 Record() *query.FluxRecord 想要 Record() FluxRecord
我希望 FluxRecord 满足 *query.FluxRecord 类型,反之亦然。我不知道还能如何改变这一点。
我正在尝试测试这个功能:
func parseInfluxData(queryResult QueryTableResult) (RecordResult, error) {
var formattedRecordResult RecordResult
for queryResult.Next() {
record := queryResult.Record()
timestamp := record.Time().UTC()
value := record.Value()
// Format the timestamp as desired
formattedTime := timestamp.Format(time.RFC3339)
// Convert the value to an integer
formattedValue, err := strconv.Atoi(fmt.Sprintf("%v", value))
if err != nil {
return formattedRecordResult, err
}
formattedRecordResult.Time = formattedTime
formattedRecordResult.Value = formattedValue
}
if queryResult.Err() != nil {
fmt.Printf("Query error: %s\n", queryResult.Err().Error())
}
return formattedRecordResult, nil
}
这是我的测试:
func TestParseInfluxData(t *testing.T) {
// Create a new instance of the QueryTableResult mock
queryTableResultMock := mocks.NewQueryTableResult(t)
// Create a new instance of the FluxRecord mock
fluxRecordMock := mocks.NewFluxRecord(t)
// Set up the Record method of the QueryTableResult mock to return the FluxRecord mock
queryTableResultMock.On("Record").Return(fluxRecordMock)
// Define the expected behavior of the mock's methods
queryTableResultMock.On("Next").Return(true).Once()
// queryTableResultMock.On("Next").Return(false).Once()
// Define the expected behavior of FluxRecord's Time() method
expectedTime := time.Date(2023, 7, 15, 12, 34, 56, 0, time.UTC)
fluxRecordMock.On("Time").Return(expectedTime)
// Define the expected behavior of FluxRecord's Value() method
expectedValue := 42
fluxRecordMock.On("Value").Return(expectedValue)
// Call the function being tested
result, err := parseInfluxData(queryTableResultMock)
assert.Nil(t, err)
// Assertions based on the expected result and error
assert.Equal(t, expectedTime.Format(time.RFC3339), result.Time)
assert.Equal(t, expectedValue, result.Value)
// Assert that all expected methods of the mock have been called
queryTableResultMock.AssertExpectations(t)
fluxRecordMock.AssertExpectations(t)
}
测试失败并出现此错误:
./parseInfluxData_test.go:34:33:无法使用 queryTableResultMock(*mocks.QueryTableResult 类型的变量)作为 parseInfluxData 参数中的 QueryTableResult 值:*mocks.QueryTableResult 未实现 QueryTableResult(Record 方法的类型错误) 有 Record() *mocks.FluxRecord 想要 Record() FluxRecord
以下是模拟:
// Code generated by mockery v2.32.0. DO NOT EDIT.
package mocks
import (
time "time"
"fmt"
mock "github.com/stretchr/testify/mock"
)
// FluxRecord is an autogenerated mock type for the FluxRecord type
type FluxRecord struct {
mock.Mock
}
// Time provides a mock function with given fields:
func (_m *FluxRecord) Time() time.Time {
ret := _m.Called()
var r0 time.Time
if rf, ok := ret.Get(0).(func() time.Time); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(time.Time)
}
return r0
}
// Value provides a mock function with given fields:
func (_m *FluxRecord) Value() interface{} {
fmt.Println("Time() method called")
ret := _m.Called()
var r0 interface{}
if rf, ok := ret.Get(0).(func() interface{}); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{})
}
}
return r0
}
// NewFluxRecord creates a new instance of FluxRecord. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFluxRecord(t interface {
mock.TestingT
Cleanup(func())
}) *FluxRecord {
mock := &FluxRecord{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Code generated by mockery v2.32.0. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
)
// QueryTableResult is an autogenerated mock type for the QueryTableResult type
type QueryTableResult struct {
mock.Mock
}
// Err provides a mock function with given fields:
func (_m *QueryTableResult) Err() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Next provides a mock function with given fields:
func (_m *QueryTableResult) Next() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Record provides a mock function with given fields:
func (_m *QueryTableResult) Record() *FluxRecord {
ret := _m.Called()
var r0 *FluxRecord
if rf, ok := ret.Get(0).(func() *FluxRecord); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*FluxRecord)
}
}
return r0
}
// NewQueryTableResult creates a new instance of QueryTableResult. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewQueryTableResult(t interface {
mock.TestingT
Cleanup(func())
}) *QueryTableResult {
mock := &QueryTableResult{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
如何实现接口替换以实现代码中的可测试性?或者我应该用另一种方法来代替吗?
所以我在这里尝试自己做。 我发现您可能面临一些循环依赖问题。
这就是我解决问题的方法
文件夹结构:
|
|__querytable
|__querytable.go // Here is where resides your interfaces FluxRecord and Query table
|__mocks
|__FluxRecord.go // this is generated by mock
|__QueryTableResult.go // this is generated by mock
|__test
|__test_test.go // The actual folder that you function parseInfluxData resides
以这种方式组织它的运作方式。您可以探索其他替代方案来构建文件。我没有更改您的代码,但我更新了模拟生成的结构。我希望这对你有用。
// Code generated by mockery v2.32.2. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
test "your_path_to_the_query_table_package/querytable"
)
// QueryTableResult is an autogenerated mock type for the QueryTableResult type
type QueryTableResult struct {
mock.Mock
}
// Err provides a mock function with given fields:
func (_m *QueryTableResult) Err() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Next provides a mock function with given fields:
func (_m *QueryTableResult) Next() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Record provides a mock function with given fields:
func (_m *QueryTableResult) Record() test.FluxRecord {
ret := _m.Called()
var r0 test.FluxRecord
if rf, ok := ret.Get(0).(func() test.FluxRecord); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(test.FluxRecord)
}
}
return r0
}
// NewQueryTableResult creates a new instance of QueryTableResult. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewQueryTableResult(t interface {
mock.TestingT
Cleanup(func())
}) *QueryTableResult {
mock := &QueryTableResult{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Code generated by mockery v2.32.2. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
time "time"
)
// FluxRecord is an autogenerated mock type for the FluxRecord type
type FluxRecord struct {
mock.Mock
}
// Time provides a mock function with given fields:
func (_m *FluxRecord) Time() time.Time {
ret := _m.Called()
var r0 time.Time
if rf, ok := ret.Get(0).(func() time.Time); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(time.Time)
}
return r0
}
// Value provides a mock function with given fields:
func (_m *FluxRecord) Value() interface{} {
ret := _m.Called()
var r0 interface{}
if rf, ok := ret.Get(0).(func() interface{}); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{})
}
}
return r0
}
// NewFluxRecord creates a new instance of FluxRecord. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFluxRecord(t interface {
mock.TestingT
Cleanup(func())
}) *FluxRecord {
mock := &FluxRecord{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}