Git Product home page Git Product logo

Comments (5)

dosubot avatar dosubot commented on May 28, 2024

🤖

你好,@snai1557!我是Dosu,一个专门为Kratos仓库提供帮助的机器人。我可以帮助你解决问题,回答你的问题,甚至帮助你成为一个贡献者。在人工维护者回复你之前,我会在这里提供帮助。我正在查看你的问题,很快会给你一个完整的答案。

from kratos.

kratos-ci-bot avatar kratos-ci-bot commented on May 28, 2024

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.

kratos-ci-bot avatar kratos-ci-bot commented on May 28, 2024

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.

kvii avatar kvii commented on May 28, 2024

没有什么黑魔法,普通工程咋测,kratos 就咋测。go 官方的 test 就足够用了,甚至连 testify 之类的都不需要。

我基于默认模板写了个例子。按照下面的步骤自己跑一遍玩一下。

  1. 执行 kratos new playground 创建工程。
  2. 把后面的的代码复制到 internal/biz/greeter_test.go。
  3. 在工程根目录执行 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.

snai1557 avatar snai1557 commented on May 28, 2024

没有什么黑魔法,普通工程咋测,kratos 就咋测。go 官方的 test 就足够用了,甚至连 testify 之类的都不需要。

我基于默认模板写了个例子。按照下面的步骤自己跑一遍玩一下。

  1. 执行 kratos new playground 创建工程。
  2. 把后面的的代码复制到 internal/biz/greeter_test.go。
  3. 在工程根目录执行 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)
}

测试方面是没什么问题。实际使用的时候创建 GreeterUsecase 可能不是一件容易的事情,主要是想了解有没有简便的方式创建 GreeterUsecase 来测试。举个例子,从 config.dev 中读取配置 wire 来创建 GreeterUsecase。

from kratos.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.