Comments (5)
🤖
你好,@snai1557!我是Dosu,一个专门为Kratos仓库提供帮助的机器人。我可以帮助你解决问题,回答你的问题,甚至帮助你成为一个贡献者。在人工维护者回复你之前,我会在这里提供帮助。我正在查看你的问题,很快会给你一个完整的答案。
from kratos.
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
Want to do unit testing on biz. Is there a simpler testing method?
from kratos.
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
🤖
Hello, @snai1557! I'm Dosu, a bot dedicated to helping Kratos warehouses. I can help you troubleshoot, answer your questions, and even help you become a contributor. I'll be here to help until a human maintainer gets back to you. I'm looking at your question and will give you a complete answer soon.
from kratos.
没有什么黑魔法,普通工程咋测,kratos 就咋测。go 官方的 test 就足够用了,甚至连 testify 之类的都不需要。
我基于默认模板写了个例子。按照下面的步骤自己跑一遍玩一下。
- 执行
kratos new playground
创建工程。 - 把后面的的代码复制到 internal/biz/greeter_test.go。
- 在工程根目录执行
go test ./internal/biz
测试 biz 逻辑。
成功的话你会看到这个结果。
ok playground/internal/biz 0.313s
translation:
No "black magic". You can just test it like a regular project, even no need testify.
I've written a demo for it. You can reproduce it using steps above.
代码:
package biz
import (
"context"
"io"
"reflect"
"testing"
"github.com/go-kratos/kratos/v2/log"
)
// 想测 GreeterUsecase 的 CreateGreeter 方法,就按下面这个格式写。
func TestGreeterUsecase_CreateGreeter(t *testing.T) {
// testCases 定义了所有测试用例。
// 可能这个写法看起来有点怪,但实际上就是个元素是匿名结构体的切片。
testCases := []struct {
name string // 测试名称
create func() *GreeterUsecase // 构造函数
ctx context.Context // 参数 1
arg *Greeter // 参数 2
want *Greeter // 期望响应
err error // 期望错误
}{
{
name: "normal",
create: func() *GreeterUsecase {
// 正常情况下怎么创建 use case 对象,测试时就怎么创建。
// 因为你测试的是 biz 而不是 repo。所以应该自己创建 stub repo。
// 如果想测执行了什么 sql 的话,去 data 层测 repo 去。
//
// 可能你会担心 stub 和现实不一样导致测不出 bug 来。
// 那你就应该在 GreeterRepo 接口的文档注释中写明白
// 传什么参数返什么结果;有什么问题报什么错。让 stub 和实际业务保持一致。
//
// 即使实际就是和接口的文档注释不一致,真出了 bug。
// 那也应该是让 repo 实现和文档一致,和 biz 没有关系,不是么?
//
// 比如 GreeterRepo 的接口文档可以这么写:
//
// // GreeterRepo 接口定义了用户数据访问接口。
// type GreeterRepo interface {
// // Save 方法将用户保存到数据库中,并返回保存后的用户。
// // 如果……,则返回 ErrUserNotFound。
// Save(context.Context, *Greeter) (*Greeter, error)
// }
var stub = &stubGreeterRepo{
saveFn: func(ctx context.Context, a *Greeter) (*Greeter, error) {
return a, nil
},
}
return NewGreeterUsecase(
stub,
log.NewStdLogger(io.Discard),
)
},
ctx: context.Background(),
arg: &Greeter{
Hello: "foo",
},
want: &Greeter{
Hello: "foo",
},
err: nil,
},
{
name: "not found",
create: func() *GreeterUsecase {
return NewGreeterUsecase(
&stubGreeterRepo{
saveFn: func(ctx context.Context, a *Greeter) (*Greeter, error) {
return nil, ErrUserNotFound
},
},
log.NewStdLogger(io.Discard),
)
},
ctx: context.Background(),
arg: &Greeter{
Hello: "not found",
},
want: nil,
err: ErrUserNotFound,
},
// 当线上业务出 bug,找到原因之后,就把对应的参数和返回值添加到测试里。
// 以后就肯定不会再出问题了。
}
// for 循环测试用例
for _, tc := range testCases {
// 为每个测试用例执行一个子测试
t.Run(tc.name, func(t *testing.T) {
// 构造对象,调用方法,传入期望的参数。
uc := tc.create()
res, err := uc.CreateGreeter(tc.ctx, tc.arg)
// 对比返回的结果是否与期望一致。
if err != tc.err {
t.Fatalf("err != tc.err. want %v, got %v", tc.err, err)
}
// 数值类型及其结构体是可以直接用 ”==“ 判等的,但切片、map 等类型或包含它们的类型则不能如此。
// 这些类型可以使用 reflect.DeepEqual() 进行判等操作。
if !reflect.DeepEqual(res, tc.want) {
t.Fatalf("res != tc.want. want %v, got %v", tc.want, res)
}
})
}
}
// stubGreeterRepo 实现了 GreeterRepo 接口,用于进行测试。
// 如果不传 saveFn,那么调用 Save 方法就会 panic。不必担心,就应该是这样。
// 请记住写测试就是为了发现问题的。为什么项目之前的提交不报错,到你这儿就 panic 了?
// 比起研究“黑魔法”去解决 panic,不如去查查业务逻辑到底改了什么才导致 panic 的。
type stubGreeterRepo struct {
saveFn func(context.Context, *Greeter) (*Greeter, error)
updateFn func(context.Context, *Greeter) (*Greeter, error)
findByIDFn func(context.Context, int64) (*Greeter, error)
listByHelloFn func(context.Context, string) ([]*Greeter, error)
listAllFn func(context.Context) ([]*Greeter, error)
}
func (r *stubGreeterRepo) Save(ctx context.Context, a *Greeter) (*Greeter, error) {
return r.saveFn(ctx, a)
}
func (r *stubGreeterRepo) Update(ctx context.Context, a *Greeter) (*Greeter, error) {
return r.updateFn(ctx, a)
}
func (r *stubGreeterRepo) FindByID(ctx context.Context, a int64) (*Greeter, error) {
return r.findByIDFn(ctx, a)
}
func (r *stubGreeterRepo) ListByHello(ctx context.Context, a string) ([]*Greeter, error) {
return r.listByHelloFn(ctx, a)
}
func (r *stubGreeterRepo) ListAll(ctx context.Context) ([]*Greeter, error) {
return r.listAllFn(ctx)
}
from kratos.
没有什么黑魔法,普通工程咋测,kratos 就咋测。go 官方的 test 就足够用了,甚至连 testify 之类的都不需要。
我基于默认模板写了个例子。按照下面的步骤自己跑一遍玩一下。
- 执行
kratos new playground
创建工程。- 把后面的的代码复制到 internal/biz/greeter_test.go。
- 在工程根目录执行
go test ./internal/biz
测试 biz 逻辑。成功的话你会看到这个结果。
ok playground/internal/biz 0.313stranslation: No "black magic". You can just test it like a regular project, even no need testify. I've written a demo for it. You can reproduce it using steps above.
代码:
package biz import ( "context" "io" "reflect" "testing" "github.com/go-kratos/kratos/v2/log" ) // 想测 GreeterUsecase 的 CreateGreeter 方法,就按下面这个格式写。 func TestGreeterUsecase_CreateGreeter(t *testing.T) { // testCases 定义了所有测试用例。 // 可能这个写法看起来有点怪,但实际上就是个元素是匿名结构体的切片。 testCases := []struct { name string // 测试名称 create func() *GreeterUsecase // 构造函数 ctx context.Context // 参数 1 arg *Greeter // 参数 2 want *Greeter // 期望响应 err error // 期望错误 }{ { name: "normal", create: func() *GreeterUsecase { // 正常情况下怎么创建 use case 对象,测试时就怎么创建。 // 因为你测试的是 biz 而不是 repo。所以应该自己创建 stub repo。 // 如果想测执行了什么 sql 的话,去 data 层测 repo 去。 // // 可能你会担心 stub 和现实不一样导致测不出 bug 来。 // 那你就应该在 GreeterRepo 接口的文档注释中写明白 // 传什么参数返什么结果;有什么问题报什么错。让 stub 和实际业务保持一致。 // // 即使实际就是和接口的文档注释不一致,真出了 bug。 // 那也应该是让 repo 实现和文档一致,和 biz 没有关系,不是么? // // 比如 GreeterRepo 的接口文档可以这么写: // // // GreeterRepo 接口定义了用户数据访问接口。 // type GreeterRepo interface { // // Save 方法将用户保存到数据库中,并返回保存后的用户。 // // 如果……,则返回 ErrUserNotFound。 // Save(context.Context, *Greeter) (*Greeter, error) // } var stub = &stubGreeterRepo{ saveFn: func(ctx context.Context, a *Greeter) (*Greeter, error) { return a, nil }, } return NewGreeterUsecase( stub, log.NewStdLogger(io.Discard), ) }, ctx: context.Background(), arg: &Greeter{ Hello: "foo", }, want: &Greeter{ Hello: "foo", }, err: nil, }, { name: "not found", create: func() *GreeterUsecase { return NewGreeterUsecase( &stubGreeterRepo{ saveFn: func(ctx context.Context, a *Greeter) (*Greeter, error) { return nil, ErrUserNotFound }, }, log.NewStdLogger(io.Discard), ) }, ctx: context.Background(), arg: &Greeter{ Hello: "not found", }, want: nil, err: ErrUserNotFound, }, // 当线上业务出 bug,找到原因之后,就把对应的参数和返回值添加到测试里。 // 以后就肯定不会再出问题了。 } // for 循环测试用例 for _, tc := range testCases { // 为每个测试用例执行一个子测试 t.Run(tc.name, func(t *testing.T) { // 构造对象,调用方法,传入期望的参数。 uc := tc.create() res, err := uc.CreateGreeter(tc.ctx, tc.arg) // 对比返回的结果是否与期望一致。 if err != tc.err { t.Fatalf("err != tc.err. want %v, got %v", tc.err, err) } // 数值类型及其结构体是可以直接用 ”==“ 判等的,但切片、map 等类型或包含它们的类型则不能如此。 // 这些类型可以使用 reflect.DeepEqual() 进行判等操作。 if !reflect.DeepEqual(res, tc.want) { t.Fatalf("res != tc.want. want %v, got %v", tc.want, res) } }) } } // stubGreeterRepo 实现了 GreeterRepo 接口,用于进行测试。 // 如果不传 saveFn,那么调用 Save 方法就会 panic。不必担心,就应该是这样。 // 请记住写测试就是为了发现问题的。为什么项目之前的提交不报错,到你这儿就 panic 了? // 比起研究“黑魔法”去解决 panic,不如去查查业务逻辑到底改了什么才导致 panic 的。 type stubGreeterRepo struct { saveFn func(context.Context, *Greeter) (*Greeter, error) updateFn func(context.Context, *Greeter) (*Greeter, error) findByIDFn func(context.Context, int64) (*Greeter, error) listByHelloFn func(context.Context, string) ([]*Greeter, error) listAllFn func(context.Context) ([]*Greeter, error) } func (r *stubGreeterRepo) Save(ctx context.Context, a *Greeter) (*Greeter, error) { return r.saveFn(ctx, a) } func (r *stubGreeterRepo) Update(ctx context.Context, a *Greeter) (*Greeter, error) { return r.updateFn(ctx, a) } func (r *stubGreeterRepo) FindByID(ctx context.Context, a int64) (*Greeter, error) { return r.findByIDFn(ctx, a) } func (r *stubGreeterRepo) ListByHello(ctx context.Context, a string) ([]*Greeter, error) { return r.listByHelloFn(ctx, a) } func (r *stubGreeterRepo) ListAll(ctx context.Context) ([]*Greeter, error) { return r.listAllFn(ctx) }
测试方面是没什么问题。实际使用的时候创建 GreeterUsecase 可能不是一件容易的事情,主要是想了解有没有简便的方式创建 GreeterUsecase 来测试。举个例子,从 config.dev 中读取配置 wire 来创建 GreeterUsecase。
from kratos.
Related Issues (20)
- [Feature] consule register need suport tags[] HOT 2
- When the App stops, the ctx in the afterStop callback is not available HOT 3
- [Question] How to return other Status Code in a successful HTTP request? HOT 7
- [Feature] Add Unwrap to custom response writer. HOT 3
- [Proposal] config feature roadmap HOT 22
- [Feature] replace kratos metrics with otel metrics HOT 3
- [The modification of header file information of http response does not take effect] ctx.Header().Set("Content-Type", "application/octet-stream") HOT 5
- [Question] How to return a custom response json? HOT 1
- Package version dependency issue HOT 4
- go generate ./...failed HOT 5
- [Feature]Add svrType related fields in proto to protoc-gen-go-http and http transport HOT 3
- proto file used to build the api document reported an error HOT 1
- consul watch, the server is offline and will not receive any notification HOT 6
- [Feature] Add NewClient method to gRPC Client HOT 3
- [Question] DDD的最佳实践方式?包括CQRS、Clean Architecture、六边形结构 HOT 5
- [Question]请教下kratos如何定义支持上传文件接口? HOT 2
- metrics statistics and usage issues [Question] HOT 5
- `stream` response in proto failed to generate http client HOT 3
- [Feature] Add kratos errors to buf remote plugin HOT 1
- [Question] I modified it based on examples/helloworld and found that client/main.go reported an error when executing. I am not sure if it is a BUG. HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from kratos.