gogf / gf

GoFrame is a modular, powerful, high-performance and enterprise-class application development framework of Golang.
https://goframe.org
MIT License
11.7k stars 1.59k forks source link

How to test the API written by ghttp #515

Closed ruinshe closed 4 years ago

ruinshe commented 4 years ago

1. What version of Go and system type/arch are you using?

Latest go binary.

go version go1.13.8 linux/amd64

2. What version of GoFrame are you using?

github.com/gogf/gf v1.11.4

3. Can this issue be reproduced with the latest release?

Yes

4. What did you do?

I'm trying to write a test case to test the functionality of my restful API, and found some sample code by searching by myself in this repository (e.g. the test of unit param json for ghttp).

5. What did you expect to see?

I didn't find a correct way to test / mock the ghttp request and response, do you have any idea?

Using the ghttp client directly may be a choice but I want to know whether it's the correct way to write test cases against ghttp library.

6. What did you see instead?

I didn't find any useful stuffs in the document website.

gqcn commented 4 years ago

@ruinshe Actually we did not write any unit testing cases in our projects. Few of internet companies would like doing this, as I know. It takes time and cost, and the product requirements change quickly during development for internet companies. Besides, we have professional testing group members, they'll write and mock API testing cases using professional third-party tools, and they also prefer Python.

We can write unit testing cases for some usage scenarios, which do not change quickly, like the base/standard libraries. We write less and keep it simple, and the gtest library is simple enough for usage. Do not mock too much, do not write unit testing cases for your changed quickly business codes, you'll regret as me ever did.

As writing simple HTTP unit testing cases, you can refer to the unit testing cases in package ghttp: https://github.com/gogf/gf/tree/master/net/ghttp

ruinshe commented 4 years ago

@gqcn Yep, actually I wrote test cases because I care about the response format, or some detailed logic inside the API controlling layer (i.e. api folder. although we should add most of them into service layer).

Besides, currently I setup simple HTTP based test cases by manually register part of routers then test them using real API server. In this case, I met another problem that I don't know the port of the server, do you have any idea?

I figured out the samples in ghttp package, you start the server using ports.PopRand() and I found the ports is defined implicitly inside the package.

gqcn commented 4 years ago

@ruinshe You can start the real business server inside your unit testing cases using package initialization function init, which will be executed once before any testing cases start. Take one of my unit testing case of gRPC server for example:

const (
    serverAddress = "127.0.0.1:8000"
    clientAddress = serverAddress
)

var (
    client service.HistoryClient
)

func init() {
    g.Config().AddPath("../../")

    serverErr := error(nil)
    go func() {
        boot.Run(serverAddress)
    }()
    time.Sleep(time.Second)
    gtest.Assert(serverErr, nil)

    conn, err := grpcwrapper.NewClient(clientAddress)
    gtest.Assert(err, nil)
    client = service.NewHistoryClient(conn)
}

func Test_AddWithCallbackJson(t *testing.T) {
    content := `
{"MsgBody":[{"MsgType":"TIMTextElem","MsgContent":{"Text":"doctorid 10000 \u53d1\u6d88\u606f\u6d4b\u8bd5 MsgSeq"}}],"CallbackCommand":"C2C.CallbackAfterSendMsg","From_Account":"hc10000","To_Account":"hc10164","MsgRandom":1937929593,"MsgSeq":1944254033,"MsgTime":1571984439,"ClientIP":"182.148.13.146","OptPlatform":"RESTAPI","RequestId":"3369661-144115214702165302-1571984439-1937929593","SdkAppid":"1400229158","contenttype":"json"}
`
    for _, s := range gstr.SplitAndTrim(gstr.Trim(content), "\n") {
        gtest.Case(t, func() {
            request := &service.AddWithCallbackJsonRequest{
                Json: s,
            }
            _, err := client.AddWithCallbackJson(context.Background(), request)
            gtest.Assert(err, nil)
        })
    }
}

You can change this by reading the server address from configuration file in your init function and assign it to your local variable.

ruinshe commented 4 years ago

@gqcn Yep, you are right, here the port may be occupied by other process, so I'm asking how to get the "real server listening port" ;-)

ruinshe commented 4 years ago

On the other hand, do you know how to fake the persistence layer (i.e. model, or directly DB) when testing?

gqcn commented 4 years ago

@gqcn Yep, you are right, here the port may be occupied by other process, so I'm asking how to get the "real server listening port" ;-)

Your web server surely has only one port configured, read and use it in your init function, do not take my random and multiple port cases for your unit testing.

On the other hand, do you know how to fake the persistence layer (i.e. model, or directly DB) when testing?

I think this one you ask is not really unit testing, but integration testing, that is responded by your testing group members.

ruinshe commented 4 years ago

@gqcn Yep, you are right, here the port may be occupied by other process, so I'm asking how to get the "real server listening port" ;-)

Your web server surely has only one port configured, read and use it in your init function, do not take my random and multiple port cases for your unit testing.

Anyway, I think it's possible using other library to get a random unused port in the server for testing (before starting the server), although it sees fixed port is not very good when writing tests because it may be occupied by others.

On the other hand, do you know how to fake the persistence layer (i.e. model, or directly DB) when testing?

I think this one you ask is not really unit testing, but integration testing, that is responded by your testing group members.

Actually it's still a unit test because we only care about the functionality of the logic code other than the persistence layer implementation. For example, we need to test a entity validation then saving logic in one function

func ValidateThenSave(entity* model.Entity) {
    var persisted *model.Entity
    model.Model.Structs(&persisted, ...)
    // validation logic here

    // then save the entity
    model.Save(entity)
}

we need to: