【问题标题】:What's the most idiomatic way of testing functions with HTTP requests in Go?在 Go 中使用 HTTP 请求测试函数最惯用的方法是什么?
【发布时间】:2021-07-21 06:48:50
【问题描述】:

我有一个有趣的小天气应用程序。只需 99 美元/天,该应用就会每天查看天气,如果西雅图下雨,就给圣地亚哥的人们送一把雨伞。

我将这两个函数用作我的应用程序的一部分:

func IsRaining() (bool, error) {
    resp, err := http.Get("https://isitraining.in/Seattle")
    if err != nil {
        return false, fmt.Errorf("could not fetch raining status: %w", err)
    }

    parsed, err := weather.Parse(resp)
    if err != nil {
        return false, fmt.Errorf("could not parse the weather: %w", err)
    }

    return parsed.IsRaining, nil
}

func SendUmbrella() error {
    postData := umbrellaPostData()
    resp, err := http.Post("https://amazon.com", "text/html", &postData)
    if err != nil {
        return fmt.Errorf("could not send umbrella: %w", err)
    }
    return nil
}

我想测试IsRaining()SendUmbrella(),但我不想每次运行测试时都需要送伞;我的工程师使用 TDD,我确实有预算,你知道的。与IsRaining() 相同,如果互联网中断了怎么办?我仍然需要能够经受住考验,风雨无阻。

我想以这样一种方式来做这件事,即代码保持符合人体工程学和可读性,但我肯定需要能够测试那些依赖于 HTTP 的函数。在 Go 中最惯用的方法是什么?

附:我正在使用Testify。告诉我所有关于我如何在 cmets 中失去惯用 Go 的希望 :)

【问题讨论】:

  • 我在设计您的问题时赞成原创性!
  • 另外,要实际回答您的问题:通过模拟您的 HTTP 客户端:thegreatcodeadventure.com/mocking-http-requests-in-golang - 文章的最终结论可以概括为:使用依赖注入并与服务交互(其中“服务”是类似于 HTTP 客户端类型 - 不是 Web 服务)仅通过接口。顺便说一句,这可能会让你开始一场重大的重构狂欢——尤其是在你的应用程序还没有使用 DI 的情况下。不幸的是,我不相信 Golang 带有自以为是的 DI 容器... lemme check
  • 更新:从这个问题来看,Go 社区似乎还没有找到一个好的 DI 解决方案:stackoverflow.com/questions/41900053/… 尽管谷歌在 2018 年推出了自己的 DI 容器:blog.golang.org/wire
  • 鉴于您的用例,isRaining 不能总是返回 true 吗?
  • 使用 testify 是“惯用”的反面,那何必呢?

标签: unit-testing go testing testify


【解决方案1】:

我不知道“最惯用的”,但与任何其他语言一样,硬编码的 classes 包令人头疼。不要直接在 http 包上调用方法,而是创建一个 httpClient 接口。然后模拟 httpClient 接口。

您可以将 httpClient 传递给函数,但将它们转换为结构上的方法更有意义。

// Set up an interface for your http client, same as http.Client.
type httpClient interface {
    Get(string) (*http.Response, error)
}

// Make a struct to hang the client and methods off of.
type umbrellaGiver struct {
    client httpClient
}

// A cut down example method.
func (giver umbrellaGiver) getExample() ([]byte, error) {
    resp, err := giver.client.Get("https://example.com")
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}

然后可以将一个模拟的 httpClient 放入您的 armourGiver 中。

// Our mocked client.
type mockedClient struct {
    mock.Mock
}

// Define the basic mocked Get method to record its arguments and
// return its mocked values.
func (m mockedClient) Get(url string) (*http.Response, error) {
    args := m.Called(url)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    } else {
        return args.Get(0).(*http.Response), args.Error(1)
    }
}

func main() {
    // Make a mockedClient and set up an expectation.
    client := new(mockedClient)

    // Make an umbrellaGiver which uses the mocked client.
    s := umbrellaGiver { client: client }

    // Let's test what happens when the call fails.
    client.On(
        "Get", "https://example.com",
    ).Return(
        nil, errors.New("The system is down"),
    )

    body, err := s.getExample()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s", body)
}

Mocking HTTP Requests in Golang

【讨论】:

    猜你喜欢
    • 2015-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-04
    • 2012-11-10
    • 2019-03-03
    • 2010-09-29
    相关资源
    最近更新 更多