比较Go中单元测试的2个错误

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

我遇到了如下问题:编写单元测试时比较 2 个错误

package main

import (
    "errors"
    "fmt"
    "reflect"
    
    "github.com/google/go-cmp/cmp"
    "github.com/google/go-cmp/cmp/cmpopts"
)

func main() {
    er1 := errors.New("database name is not exists")
    er2 := errors.New("database name is not exists")
    
    result1 := reflect.DeepEqual(er1, er2)
    fmt.Println("reflect: ", result1)
    
    result2 := cmp.Equal(er1, er2, cmpopts.EquateErrors())
    fmt.Println("compare: ", result2)
    
    result3 := errors.Is(er1, er2)
    fmt.Println("errorIs: ", result3)
}

上面代码的输出是:

reflect:  true
compare:  false
errorIs:  false

我想比较 2 个错误,

reflect.DeepEqual(er1, er2)
是我应用的第一种方法,此方法产生我想要的输出,但此方法有来自
go lint
的警告:

avoid using reflect.DeepEqual with errorsdeepequalerrors

经过google搜索,有人告诉我一些方法:

  • 使用cmp包来比较:
    cmp.Equal(er1, er2, cmpopts.EquateErrors())
  • 使用errors包来比较:
    errors.Is(er1, er2)

但是上述两种方法都不能产生与第一种方法相同的结果(使用reflect.DeepEqual)。 我如何在没有

go lint
警告的情况下比较 2 个错误,并产生像
reflect.DeepEqual
这样的结果 谢

go
3个回答
4
投票

根据您编写测试的方式,您可能会依赖

reflect.DeepEqual()
并忽略 linter 警告;
缺点是:您开始依赖于您返回的错误的内部结构。


在我们编写的测试代码中,我们使用以下模式之一:

  • 大多数时候,我们只是将误差与
    nil
    ;
  • 进行比较
  • 在某些情况下,我们的函数会返回预定义的错误值,我们会测试这些特定值:
package pkg

var ErrUnboltedGizmo = errors.New("gizmo is unbolted")

// in test functions, depending on the case :
if err == pkg.ErrUnboltedGizmo { ...
// or :
if errors.Is(err, pkg.ErrUnboltedGizmo) { ...
  • 当我们的生产代码要求发现特定错误时(常见的用例是
    io.EOF
    ),我们编写的代码会尽职尽责地包装该已知错误,并且我们使用
    errors.Is()
    (在生产代码和测试代码中) ),
  • 当仅在测试中需要松散地确认错误与某些内容匹配而不是其他内容时(例如:
    Parse error
    而不是
    File not found
    ),我们只需在错误消息中搜索字符串即可:
if err == nil || !strings.Contains(err.Error(), "database name is not exists") {
    t.Errorf("unexpected error : %s", err)
}

1
投票

我发现有用的是使用 cmp.Diff 和 cmpopts.IgnoreFields 来忽略导致问题的具体错误字段,然后我只需使用 string.Contains 或我认为合适的任何内容来检查错误。

所以事情是这样的:

if diff := cmp.Diff(expected, got, cmpopts.IgnoreFields(expected, "ErrorField")); diff != "" { 
          // found diff not including the error
}

现在只检查自己的错误,仅此而已。

是的,我知道你已经标记了一个解决方案,但也许它会对某人有所帮助:)


0
投票

我不确定我是否同意 Reflect.DeepEqual 应该避免错误的论点。我遇到过无法使用 Reflect.DeepEqual 的两种情况,更常见的是,我需要确定错误类型并且已经知道错误消息(即,您可以记录完整的数据库连接错误以进行内部调试)然后,您将错误替换为自定义的InternalServerErr和一条包含对用户的非敏感信息的消息,例如“无法访问内部资源”,因为用户不需要了解有关内部基础设施、数据存储等的详细信息..这可能是由数据库错误揭示的。在这种情况下,我们都有特定类型的错误和特定的词汇内容:reflect.DeepEqual

的完美用例

如果您的错误具有动态词汇内容(例如从失败的数据库连接尝试中返回的内容),您可以将已知字符串添加到错误消息中并使用 strings.Contains,如其他人所述。很多时候,这就是您

should
在测试中进行错误比较的方式。

在下面的示例中,我输出错误类型和值。这是因为我需要知道我的数据访问层返回的通用错误已转换为自定义的InternalServiceErr。

if !reflect.DeepEqual(test.expect.err, err) {
    t.Errorf(
        "\tFAIL -> expected: %T:%v, actual: %T:%v",
        test.expect.err, test.expect.err, err, err,
    )
} else {
    t.Log("\tSuccess")
}

然后我可以通过创建一个函数以在处理程序级别返回错误以及相关的 HTTP 状态来利用代码重用。 DeepEqual 非常适合测试此功能,前提是您可以预测错误的词汇内容。下面的例子:

func WriteErrorResponse(w http.ResponseWriter, err error) {

    var statusCode int

    switch err.(type) {
    case *types.BadRequestErr:
        statusCode = http.StatusBadRequest
    case *types.InternalServerErr:
        statusCode = http.StatusInternalServerError
    case *types.MethodNotAllowedErr:
        statusCode = http.StatusMethodNotAllowed
    case *types.UnAuthorized:
        statusCode = http.StatusForbidden
    case *types.UnAuthenticated:
        statusCode = http.StatusUnauthorized
    case *types.NotFoundErr:
        statusCode = http.StatusNotFound
    default:
        statusCode = http.StatusInternalServerError
    }

    WriteResponse(w, err, statusCode)

}

这在很大程度上取决于您正在创建的应用程序,但在 VS Code 中,reflect.DeepEqual 不应该被突出显示为反模式,肯定有合法的用例来比较与 Reflect.DeepEqual 的错误

© www.soinside.com 2019 - 2024. All rights reserved.