avant-propos
Récemment, j'ai vu de nombreux didacticiels sur les tests de langage Go. Ils ne parlent que de l'utilisation la plus élémentaire des tests et abordent à peine la manière de concevoir un excellent test en développement. Ce blog vous guidera étape par étape pour compléter une excellenteGo-Test
pense
Le langage Go dispose d'un ensemble de systèmes de tests unitaires et de tests de performances, qui peuvent tester rapidement un morceau de code requis avec seulement une petite quantité de code ajouté.
À quoi doit ressembler un bon test ? Il doit être libre de toute contrainte externe ou il n'affectera pas le développement formel une fois le test terminé. Et simuler la situation réelle de développement
pratique
Initialiser le projet
Après avoir ouvert un projet, nous devons d'abord le faire go mod init
, et le projet suivant tirera également des bibliothèques tierces
$ go mod init awesomeTest
Afin de simuler le scénario de développement réel, nous utilisons MongoDB
pour opérer, et afin d'éviter d'être trop compliqué, nous n'en fixons qu'unkey
Les opérations suivantes impliquent la connaissance de MongoDB. Si vous ne le connaissez pas, ne vous inquiétez pas. La méthode principale n'est pas une base de données spécifique.
Tout d'abord, nous devons l'exécuter localement . Il est MongoDB
recommandé de l'utiliser Docker
. Pour ceux qui ne le font pas, vous pouvez consulter mon blog précédent . Déployer MongoDB avec des conteneurs Docker et prendre en charge l'accès à distance
Ensuite, nous devons installer MongoDB
les dépendances tierces pour l'opération
$ go get "go.mongodb.org/mongo-driver/mongo"
Ensuite, nous créons les données dont nous avons besoin dans le projet
//main.go
package main
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
c := context.Background()
mc, err := mongo.Connect(c, options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
panic(err)
}
col := mc.Database("awesomeTest").Collection("test")
insertRows(c, col)
}
func insertRows(c context.Context, col *mongo.Collection) {
res, err := col.InsertMany(c, []interface{}{
bson.M{
"test_id": "123",
},
bson.M{
"test_id": "456",
},
})
if err != nil {
panic(err)
}
fmt.Printf("%+v", res)
}
&{InsertedIDs:[ObjectID("62ce8ed4e2aaad4e36242623") ObjectID("62ce8ed4e2aaad4e36
242624")]}
进程 已完成,退出代码为 0
Au main.go
milieu, nous en avons inséré deux test_id
et imprimé les correspondants Après les ObjID
avoir ObjID
récupérés, nous pouvons effectuer quelques tests simples.
essai primaire
Pour tester, nous devons d'abord implémenter quelques méthodes pour appeler
// mongo/mongo.go
// Mongo定义一个mongodb的数据访问对象
type Mongo struct {
col *mongo.Collection
}
// 使用NewMongo来初始化一个mongodb的数据访问对象
func NewMongo(db *mongo.Database) *Mongo {
return &Mongo{
col: db.Collection("test"),
}
}
Ensuite, nous pouvons mongodb
implémenter certaines méthodes pour l'objet d'accès aux données. Comme il s'agit d'un didacticiel, nous n'en ferons pas de très compliquées, mais ce sera certainement beaucoup plus compliqué en développement réel, mais les méthodes sont les mêmes.
// mongo/mongo.go
// 将test_id解析为ObjID
func (m *Mongo) ResolveObjID(c context.Context, testID string) (string, error) {
res := m.col.FindOneAndUpdate(c, bson.M{
"test_id": testID,
}, bson.M{
"$set": bson.M{
"test_id": testID,
},
}, options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After))
if err := res.Err(); err != nil {
return "", fmt.Errorf("cannot findOneAndUpdate: %v", err)
}
var row struct {
ID primitive.ObjectID `bson:"_id"`
}
err := res.Decode(&row)
if err != nil {
return "", fmt.Errorf("cannot decode result: %v", err)
}
return row.ID.Hex(), nil
}
Lorsque nous passons le contexte et testID
la méthode dans cette méthode, nous MongoDB
interrogeons le correspondant ObjID
et return
sortons. S'il y a une erreur, l'erreur sera imprimée directement et le programme se terminera.
完成这些简单的代码之后就可以开始初级测试,一个非常基础的测试,但可以直接检验方法的正确与否而不需要实例调用
// mongo/mongo_test.go
func TestMongo_ResolveAccountID(t *testing.T) {
c := context.Background()
mc, err := mongo.Connect(c, options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
t.Fatalf("cannot connect mongodb: %v", err)
}
m := NewMongo(mc.Database("awesomeTest"))
id, err := m.ResolveObjID(c, "123")
if err != nil {
t.Errorf("faild resolve Obj id for 123: %v", err)
} else {
want := "62ce8ed4e2aaad4e36242623"
if id != want {
t.Errorf("resolve Obj id: want: %q, got: %q", want, id)
}
}
}
上述测试样例先连接了数据库并在测试中新建了一个MongoDB
的数据访问对象,然后将test_id
传了进去并解析出相应的ObjID
,若未解析成功或答案不一致则测试失败
再次思考
完成了上述测试之后我们会想,如果这样进行测试的话会连接外部的数据库,甚至是开发中使用的数据库。一个完美的测试应该不会依赖外界或对外界有造成改变的可能,对此我想到了使用现在非常流行的容器工具Docker
,在测试开始时,我们新建一个MongoDB
的容器,在测试结束之后将其关闭,这样就能完美实现我们的想法
Docker
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows操作系统的机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口
关于Docker
就介绍到这里,如果不会使用的同学同上,可以去看看我Docker
相关的文章
为了实践我们上面所想到操作,我们先新建一个Docker
文件夹进行实验docker/main.go
首先我们拉取在Go语言中操作Docker
的相关第三方包
$ go get -u "github.com/docker/docker"
大概的思路就是先新建一个新的Docker
镜像并给他找一个空的端口运行,预计了一下大概的测试用时以及其他的时间,我们稳妥地让它存活五秒钟,在时间到后立马将其销毁
package main
import (
"context"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"time"
)
func main() {
c, err := client.NewClientWithOpts()
if err != nil {
panic(err)
}
ctx := context.Background()
resp, err := c.ContainerCreate(ctx, &container.Config{
Image: "mongo:latest",
ExposedPorts: nat.PortSet{
"27017/tcp": {},
},
}, &container.HostConfig{
PortBindings: nat.PortMap{
"27017/tcp": []nat.PortBinding{
{
HostIP: "127.0.0.1",
HostPort: "0", //随意找一个空的端口
},
},
},
}, nil, nil, "")
if err != nil {
panic(err)
}
err = c.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
if err != nil {
panic(err)
}
fmt.Println("container started")
time.Sleep(5 * time.Second)
inspRes, err := c.ContainerInspect(ctx, resp.ID)
if err != nil {
panic(err)
}
fmt.Printf("listening at %+v\n",
inspRes.NetworkSettings.Ports["27017/tcp"][0])
fmt.Println("killing container")
err = c.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{
Force: true,
})
if err != nil {
panic(err)
}
}
用Go语言简单实现了一下,并不复杂
进阶测试
在思路理清晰之后我们就要对刚才的测试进行改动,所谓进阶测试当然不可能只测试一组数据,我们会使用到表格驱动测试
回到刚才的Docker
操作,我们不可能将上面那么多的代码都放进测试中,所以我们新建一个mongotesting.go
文件将此类函数封装在外部
首先是在Docker
中跑MongoDB
的函数,与上文的区别不大,主要是在创建容器后的操作有所改变
//mongo/mongotesting.go
func RunWithMongoInDocker(m *testing.M) int {
...
containerID := resp.ID
defer func() {
err := c.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
Force: true,
})
if err != nil {
panic(err)
}
}()
err = c.ContainerStart(ctx, containerID, types.ContainerStartOptions{})
if err != nil {
panic(err)
}
inspRes, err := c.ContainerInspect(ctx, containerID)
if err != nil {
panic(err)
}
hostPort := inspRes.NetworkSettings.Ports["27017/tcp"][0]
mongoURI = fmt.Sprintf("mongodb://%s:%s", hostPort.HostIP, hostPort.HostPort)
return m.Run()
}
调用了一个defer
使其在return之后就删除掉
接下来是在Docker
中与MongoDB
创建一个新连接的函数
//mongo/mongotesting.go
func NewClient(c context.Context) (*mongo.Client, error) {
if mongoURI == "" {
return nil, fmt.Errorf("mong uri not set. Please run RunWithMongoInDocker in TestMain")
}
return mongo.Connect(c, options.Client().ApplyURI(mongoURI))
}
最后是创建ObjID的函数,也是非常简单的调用
//mongo/mongotesting.go
func mustObjID(hex string) primitive.ObjectID {
ObjID, err := primitive.ObjectIDFromHex(hex)
if err != nil {
panic(err)
}
return ObjID
}
完成这一步准备之后我们就可以开始写最终的测试代码
在此之前我们再缕一缕思路,我们的想法是在开始测试之前开启一个MongoDB
的Docker
容器,然后测试结束时自动关闭。测试中呢我们使用表格驱动测试,使用两组数据来测试。开始测试后我们先起一个MongoDB
的连接,然后新建一个test
数据库并插入两组数据,接下来写测试样例,然后用一个for range
结构跑完所有的数据并验证正确性。
我们将以上的步骤分为三步走
第一步 新建连接,插入数据
//mongo/mongo_test.go
func TestResolveObjID(t *testing.T) {
c := context.Background()
mc, err := NewClient(c)
if err != nil {
t.Fatalf("cannot connect mongodb: %v", err)
}
m := NewMongo(mc.Database("test"))
_, err = m.col.InsertMany(c, []interface{}{
bson.M{
"_id": mustObjID("5f7c245ab0361e00ffb9fd6f"),
"test_id": "testid_1",
},
bson.M{
"_id": mustObjID("5f7c245ab0361e00ffb9fd70"),
"test_id": "testid_2",
},
})
if err != nil {
t.Fatalf("cannot insert initial values: %v", err)
}
...
}
第一步中我们调用了写在mongotesting.go
中的NewClient来创建一个新的连接,并向其中加入了两组数据
第二步 加入样例,准备测试
//mongo/mongo_test.go
func TestResolveObjID(t *testing.T) {
...第一步
cases := []struct {
name string
testID string
want string
}{
{
name: "existing_user",
testID: "testid_1",
want: "5f7c245ab0361e00ffb9fd6f",
},
{
name: "another_existing_user",
testID: "testid_2",
want: "5f7c245ab0361e00ffb9fd70",
},
}
...
}
在第二步中我们使用表格驱动测试的方法放入了两个样例准备进行测试
第三步 遍历样例,使用容器
//mongo/mongo_test.go
func TestResolveObjID(t *testing.T) {
...第一步
...第二步
for _, cc := range cases {
t.Run(cc.name, func(t *testing.T) {
rid, err := m.ResolveObjID(context.Background(), cc.testID)
if err != nil {
t.Errorf("faild resolve Obj id for %q: %v", cc.testID, err)
}
if rid != cc.want {
t.Errorf("resolve Obj id: want: %q; got: %q", cc.want, rid)
}
})
}
}
在这里我们使用了for range
结构遍历了我们所有了样例,将test_id
解析成了ObjID
,成功对应后就会通过测试,反之会报错
接下来是最重要的地方,我们需要使用Docker
就还需要在测试文件中加上最后一个函数
func TestMain(m *testing.M) {
os.Exit(RunWithMongoInDocker(m))
}
在此之后我们的测试就写好了,大家可以打开Docker
来实验一下
结语
如果有没弄清楚的地方欢迎大家向我提问,我都会尽力解答
这是我的GitHub主页 github.com/L2ncE
欢迎大家Follow/Star/Fork三连
本文正在参加技术专题18期-聊聊Go语言框架