Golang中的单元测试

huangapple go评论76阅读模式
英文:

Unit testing in golang

问题

我正在翻译您提供的代码段,请稍等片刻。

我目前正在研究为我的Go服务创建一些单元测试以及构建在该功能之上的其他函数我想知道在Go中进行单元测试的最佳方法是什么我的代码如下

type BBPeripheral struct {
    client   *http.Client
    endpoint string
}

type BBQuery struct {
    Name string `json:"name"`
}

type BBResponse struct {
    Brand string `json:"brand"`
    Model string `json:"model"`
    ...
}

type Peripheral struct {
    Brand string
    Model string
    ...
}

type Service interface {
    Get(name string) (*Peripheral, error)
}

func NewBBPeripheral(config *peripheralConfig) (*BBPeripheral, error) {
    transport, err := setTransport(config)
    if err != nil {
        return nil, err
    }

    BB := &BBPeripheral{
        client:   &http.Client{Transport: transport},
        endpoint: config.Endpoint[0],
    }

    return BB, nil
}

func (this *BBPeripheral) Get(name string) (*Peripheral, error) {

    data, err := json.Marshal(BBQuery{Name: name})
    if err != nil {
        return nil, fmt.Errorf("BBPeripheral.Get Marshal: %s", err)
    }

    resp, err := this.client.Post(this.endpoint, "application/json", bytes.NewBuffer(data))
    if resp != nil {
        defer resp.Body.Close()
    }
    if err != nil {
        return nil, err
    }
    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf(resp.StatusCode)
    }

    var BBResponse BBResponse

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    err = json.Unmarshal(body, &BBResponse)
    if err != nil {
        return nil, err
    }

    peripheral := &Peripheral{}

    peripheral.Model = BBResponse.Model
    if peripheral.Model == "" {
        peripheral.Model = NA
    }

    peripheral.Brand = BBResponse.Brand
    if peripheral.Brand == "" {
        peripheral.Brand = NA
    }

    return peripheral, nil
}

测试这段代码和使用这些函数的代码的最有效方式是创建一个单独的goroutine来充当服务器,使用http.httptest包,或者其他方法?这是我第一次尝试编写测试,所以我不太清楚如何做。

英文:

I'm currently looking into creating some unit tests for my service in Go, as well as other functions that build up on top of that functionality, and I'm wondering what is the best way to unit test that in Go? My code looks like:

type BBPeripheral struct {
client   *http.Client
endpoint string
}
type BBQuery struct {
Name string `json:"name"`
}
type BBResponse struct {
Brand          string `json:"brand"`
Model          string `json:"model"`
...
}
type Peripheral struct {
Brand          string 
Model          string 
...
}
type Service interface {
Get(name string) (*Peripheral, error)
}
func NewBBPeripheral(config *peripheralConfig) (*BBPeripheral, error) {
transport, err := setTransport(config)
if err != nil {
return nil, err
}
BB := &BBPeripheral{
client:   &http.Client{Transport: transport},
endpoint: config.Endpoint[0],
}
return BB, nil
}
func (this *BBPeripheral) Get(name string) (*Peripheral, error) {
data, err := json.Marshal(BBQuery{Name: name})
if err != nil {
return nil, fmt.Errorf("BBPeripheral.Get Marshal: %s", err)
}
resp, err := this.client.Post(this.endpoint, "application/json", bytes.NewBuffer(data))
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(resp.StatusCode)
}
var BBResponse BBResponse
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &BBResponse)
if err != nil {
return nil, err
}
peripheral := &Peripheral{}
peripheral.Model = BBResponse.Model
if peripheral.Model == "" {
peripheral.Model = NA
}
peripheral.Brand = BBResponse.Brand
if peripheral.Brand == "" {
peripheral.Brand = NA
}
return peripheral, nil
}

Is the most efficient way of testing this code and the code that uses these functions to spin up a separate goroutine to act like the server, use http.httptest package, or something else? that's the first time that i try to write a test then i don't realy know how.

答案1

得分: 1

这完全取决于具体情况。Go语言提供了几乎所有你需要的工具来测试应用程序的每个层面。

单元测试

设计很重要,因为没有太多的技巧可以动态提供模拟/存根对象。你可以为测试覆盖变量,但这会带来各种清理问题。我建议你专注于无IO的单元测试,以检查你的特定逻辑是否正常工作。

例如,你可以通过将client定义为接口,在实例化时要求传入,并为测试提供一个存根对象,来测试BBPeripheral.Get方法。

func Test_BBPeripheral_Get_Success(*testing.T) {
  bb := BBPeripheral{client: &StubSuccessClient, ...}
  p, err := bb.Get(...) 
  if err != nil {
     t.Fail()
  }
}

然后,你可以创建一个存根错误客户端,以测试Get方法中的错误处理:

func Test_BBPeripheral_Get_Success(*testing.T) {
  bb := BBPeripheral{client: &StubErrClient, ...}
  _, err := bb.Get(...) 
  if err == nil {
     t.Fail()
  }
}

组件/集成测试

这些测试可以帮助验证包中的每个单元是否可以协同工作。由于你的代码通过HTTP通信,Go提供了httptest包,可以用来进行测试。

为此,测试可以创建一个带有处理程序的httptest服务器,以提供this.endpoint所期望的响应。然后,你可以使用公共接口来测试你的代码,通过请求一个NewBBPeripheral,并传入与Server.URL属性对应的this.endpoint

这样可以模拟你的代码与真实服务器通信的情况。

Go协程测试

Go语言使编写并发代码变得非常简单,并且同样容易进行测试。测试生成一个启动一个协程来执行NewBBPeripheral的顶层代码,与上面的测试非常相似。除了启动一个测试服务器外,你的测试还需要等待异步代码完成。如果你没有一种服务级别的取消/关闭/完成信号的方法,那么可能需要使用协程来测试它。

竞态条件/负载测试

使用Go语言内置的基准测试结合-race标志,你可以轻松地对代码进行测试和性能分析,以检测竞态条件,利用你上面编写的测试。


需要记住的一件事是,如果你的应用程序的实现仍在变动中,编写单元测试可能会花费大量时间。创建一些测试来测试代码的公共接口,可以轻松验证应用程序是否正常工作,同时允许实现发生变化。

英文:

It really completely depends. Go provides pretty much all the tools you need to test your application at every single level.

Unit Tests

Design is important because there aren't many tricks to dynamically provide mock/stub objects. You can override variables for tests, but it unlocks all sorts of problems with cleanup. I would focus on IO free unit tests to check that your specific logic works.

For example, you could test BBPeripheral.Get method by making client an interface, requiring it during instantiation, and providing a stub one for the test.

func Test_BBPeripheral_Get_Success(*testing.T) {
bb := BBPeripheral{client: &StubSuccessClient, ...}
p, err := bb.Get(...) 
if err != nil {
t.Fail()
}
}

Then you could create a stub error client that exercises error handling in the Get method:

func Test_BBPeripheral_Get_Success(*testing.T) {
bb := BBPeripheral{client: &StubErrClient, ...}
_, err := bb.Get(...) 
if err == nil {
t.Fail()
}
}

Component/Integration Tests

These tests can help exercise that each individual unit in your package can work together in unison. Since your code talks over http, Go provides the httptest package that could be used.

To do this the test could create an httptest server with a handler registered to provide the response that this.endpoint expects. You could then exercise your code using its public interface by requesting a NewBBPeripheral, passing in this.endpoint corresponding to the Server.URL property.

This allows you to simulate your code talking to a real server.

Go Routine Tests

Go makes it so easy to write concurrent code, and makes it just as easy to test it. Testing the top level code that spawns a go routine that exercises NewBBPeripheral could look very much like the test above. In addition to starting up a test server your test will have to wait your your asynchronous code to complete. If you don't have a service wide way to cancel/shutdown/signal complete then one may be required to test it using go routines.

RaceCondition/Load Testing

Using go's built in bechmark test combined with -race flag, you can easily exercise your code, and profile it for race conditions, leveraging the tests you wrote above.


One thing to keep in mind, if the implementation of your application is still in flux, writing unit tests may cost a large amount of time. Creating a couple tests, which exercise the public interface of your code, should allow you to easily verify that your application is working, while allowing the implementation to change.

huangapple
  • 本文由 发表于 2016年11月18日 23:49:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/40681161.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定