通过接收接口和返回结构体在 Go 中编写测试

问题描述 投票:0回答:1

我在实现 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
}

如何实现接口替换以实现代码中的可测试性?或者我应该用另一种方法来代替吗?

go influxdb mockery testify
1个回答
0
投票

所以我在这里尝试自己做。 我发现您可能面临一些循环依赖问题。

这就是我解决问题的方法

文件夹结构:

|
|__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
}
© www.soinside.com 2019 - 2024. All rights reserved.