douzhixun8393 2017-07-14 17:35
浏览 13
已采纳

我将如何测试此方法?

Essentially I've begun to work on a wrapper for the Riot Games API and I'm struggling with how to test it. I've got the repository plugged into Travis so on push it runs go test but I'm not sure how to go about testing it since the API_KEY required for the requests changes daily and I can't auto-regenerate it, i'd have to manually add it every day if I tested the endpoints directly.

So I was wondering if it was possible to mock the responses, but in that case I'm guessing I'd need to refactor my code?

So i've made a struct to represent their 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"`
}

That struct has a method:

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
}

Is it a case that the struct method doesn't have a single responsibility, in a sense it's building the endpoint, firing off the request and parsing the response. Do I need to refactor it in order to make it testable, and in which case what's the best approach for that? Should I make a Request and Response struct and then test those?

To clarify the API Keys used are rate limited and need to be regenerated daily, Riot Games do not allow you to use a crawler to auto-regenerate your keys. I'm using Travis for continuous integration so I'm wondering if there's a way to mock the request/response.

Potentially my approach is wrong, still learning.

Hopefully that all makes some form of sense, happy to clarify if not.

  • 写回答

1条回答 默认 最新

  • doujia9204 2017-07-14 18:42
    关注

    Writing unit tests consists of:

    • Providing known state for all of your inputs.
    • Testing that, given all meaning combinations of those inputs, you receive the expected outputs.

    So you need to first identify your inputs:

    • s Summoner
    • name string
    • region string

    Plus any "hidden" inputs, by way of globals:

    • client := &http.Client{}

    And your outputs are:

    • summoner *Summoner
    • err error

    (There can also be "hidden" outputs, if you write files, or change global variables, but you don't appear to do that here).

    Now the first three inputs are easy to create from scratch for your tests: Just provide an empty Summoner{} (since you don't read or set that at all in your function, there's no need to set it other than to an empty value). name and region can simply be set to strings.

    The only part remaining is your http.Client. At minimum, you should probably pass that in as an argument. Not only does this give you control over your tests, but it allows you to use easily use different client in production in the future.

    But to ease testing, you might consider actually passing in a client-like interface, which you can easily mock. The only method you call on client is Do, so you could easily create a Doer interface:

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

    Then change your function signature to take that as one argument:

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

    Now, in your test you can create a custom type that fulfills the doer interface, and responds with any http.Response you like, without needing to use a server in your tests.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 做个有关计算的小程序
  • ¥15 MPI读取tif文件无法正常给各进程分配路径
  • ¥15 如何用MATLAB实现以下三个公式(有相互嵌套)
  • ¥30 关于#算法#的问题:运用EViews第九版本进行一系列计量经济学的时间数列数据回归分析预测问题 求各位帮我解答一下
  • ¥15 setInterval 页面闪烁,怎么解决
  • ¥15 如何让企业微信机器人实现消息汇总整合
  • ¥50 关于#ui#的问题:做yolov8的ui界面出现的问题
  • ¥15 如何用Python爬取各高校教师公开的教育和工作经历
  • ¥15 TLE9879QXA40 电机驱动
  • ¥20 对于工程问题的非线性数学模型进行线性化