我该如何测试这种方法?

时间:2017-07-14 17:35:34

标签: testing go automated-tests

基本上我已经开始研究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进行持续集成,因此我想知道是否有办法模拟请求/响应。

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

希望所有人都有某种形式的感觉,如果没有,我很乐意澄清。

1 个答案:

答案 0 :(得分:4)

编写单元测试包括:

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

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

  • 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进行响应,而无需在测试中使用服务器。