【问题标题】:How would I test this method?我将如何测试这种方法?
【发布时间】:2017-12-19 21:32:51
【问题描述】:

基本上,我已经开始为 Riot Games API 设计一个包装器,但我正在为如何测试它而苦苦挣扎。我已将存储库插入 Travis,因此推送它运行 go test 但我不确定如何进行测试,因为请求所需的 API_KEY 每天都在变化,我无法自动重新生成它,我如果我直接测试端点,我必须每天手动添加它。

所以我想知道是否可以模拟响应,但在这种情况下,我猜我需要重构我的代码?

所以我制作了一个结构来代表他们的 SummonerDTO

type Summoner struct {
    ID            int64  `json:"id"`
    AccountID     int64  `json:"accountId"`
    ProfileIconID int    `json:"profileIconId"`
    Name          string `json:"name"`
    Level         int    `json:"summonerLevel"`
    RevisionDate  int64  `json:"revisionDate"`
}

该结构有一个方法:

func (s Summoner) ByName(name string, region string) (summoner *Summoner, err error) {
    endpoint := fmt.Sprintf("https://%s.api.riotgames.com/lol/summoner/%s/summoners/by-name/%s", REGIONS[region], VERSION, name)

    client := &http.Client{}
    req, err := http.NewRequest("GET", endpoint, nil)
    if err != nil {
        return nil, fmt.Errorf("unable to create new client for request: %v", err)
    }

    req.Header.Set("X-Riot-Token", API_KEY)

    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("unable to complete request to endpoint: %v", err)
    }

    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("request to api failed with: %v", resp.Status)
    }

    respBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("unable to read response body: %v", err)
    }

    if err := json.Unmarshal([]byte(respBody), &summoner); err != nil {
        return nil, fmt.Errorf("unable to unmarshal response body to summoner struct: %v", err)
    }

    return summoner, nil
}

结构方法没有单一职责的情况,在某种意义上它正在构建端点,触发请求并解析响应。我是否需要重构它以使其可测试,在这种情况下,最好的方法是什么?我应该创建一个请求和响应结构然后测试它们吗?

为了澄清所使用的 API 密钥受到速率限制并且需要每天重新生成,Riot Games 不允许您使用爬虫来自动重新生成密钥。我正在使用 Travis 进行持续集成,所以我想知道是否有办法模拟请求/响应。

可能我的方法是错误的,仍在学习中。

希望这一切都有意义,如果没有,很高兴澄清。

【问题讨论】:

    标签: testing go automated-tests


    【解决方案1】:

    编写单元测试包括:

    • 为您的所有输入提供已知状态。
    • 测试给定这些输入的所有含义组合,您会收到预期的输出。

    所以你需要首先确定你的输入:

    • s Summoner
    • name string
    • region string

    加上任何“隐藏”输入,通过全局变量:

    • client := &http.Client{}

    你的输出是:

    • summoner *Summoner
    • err error

    (如果您编写文件或更改全局变量,也可能存在“隐藏”输出,但您在这里似乎没有这样做)。

    现在,前三个输入很容易从头开始为您的测试创建:只需提供一个空的 Summoner{}(因为您根本没有在函数中读取或设置它,所以除了将其设置为一个空值)。 nameregion 可以简单地设置为字符串。

    剩下的唯一部分就是你的http.Client。至少,您应该将其作为参数传递。这不仅可以让您控制您的测试,还可以让您在未来的生产环境中轻松使用不同的客户端。

    但为了简化测试,您可能会考虑实际传递一个类似客户端的接口,您可以轻松地对其进行模拟。您在client 上调用的唯一方法是Do,因此您可以轻松创建Doer 接口:

    type doer interface {
        Do(req *Request) (*Response, error)
    }
    

    然后更改您的函数签名以将其作为一个参数:

    func (s Summoner) ByName(client doer, name string, region string) (summoner *Summoner, err error) {
    

    现在,在您的测试中,您可以创建一个满足doer 接口的自定义类型,并以您喜欢的任何http.Response 进行响应,而无需在测试中使用服务器。

    【讨论】:

    • 感谢您的回复,对我来说,这是一个彻底而深思熟虑的过程。在我接受答案之前,会给其他人一些时间来插话。
    猜你喜欢
    • 2022-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-03
    • 1970-01-01
    • 2011-04-12
    相关资源
    最近更新 更多