Golang CRUDCRUD
1. 测试 CreateAccount
account.sql.goCreateAccountdb/sqlcaccount_test.go
Golangtest
dbTestCreateAccount
GoTesttesting.T
T
package db
import "testing"
func TestCreateAccount(t *testing.T) {
}
Queries objectdb/sqlcmain_test.go
testQueries
var testQueries *Queries
TestMaintesting.M
GolangTestMain
package db
import "testing"
var testQueries *Queries
func TestMain(m *testing.M) {
}
在这里先创建与数据库的连接,目前先用硬编码的方式把dbDriver和dbSource作为常量,后面我们将改进它
package db
import (
"database/sql"
"log"
"os"
"testing"
)
var testQueries *Queries
const (
dbDriver = "postgres"
dbSource = "postgresql://root:123456@localhost:5432/simple_bank?sslmode=disable"
)
func TestMain(m *testing.M) {
conn, err := sql.Open(dbDriver, dbSource)
if err != nil {
log.Fatal("cannot connect to db:", err)
}
testQueries = New(conn)
os.Exit(m.Run())
}
m.Run()os.Exitrun testdatabase/sqlpostgres
go get github.com/lib/pq
go.modgithub.com/lib/pqindirect
main_test.gogithub.com/lib/pq_
import (
"database/sql"
"log"
"os"
"testing"
_ "github.com/lib/pq"
)
TestMainrun testgo.modindirect
go mod tidy
CreateAccountaccount_test.goTestCreateAccount
CreateAccountParams
arg := CreateAccountParams{
Owner: "张三",
Balance: 100,
Currency: "RMB",
}
testQueries.CreateAccount()arg
account, err := testQueries.CreateAccount(context.Background(), arg)
testQueriesmain_test.goaccounterr
testify
go get github.com/stretchr/testify
account_test.go"github.com/stretchr/testify/require"
require.NoError(t, err)
account
require.NotEmpty(t, account)
之后,我们要检查,账户的所有者、余额和币种是否与输入的一致:
require.Equal(t, arg.Owner, account.Owner)
require.Equal(t, arg.Balance, account.Balance)
require.Equal(t, arg.Currency, account.Currency)
IDpostgres
require.NotZero(t, account.ID)
created_at
package db
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestCreateAccount(t *testing.T) {
arg := CreateAccountParams{
Owner: "张三",
Balance: 100,
Currency: "RMB",
}
account, err := testQueries.CreateAccount(context.Background(), arg)
// err 必须为 nil
require.NoError(t, err)
// account 不能为空对象
require.NotEmpty(t, account)
// 账户的所有者、余额和币种是否与输入的一致
require.Equal(t, arg.Owner, account.Owner)
require.Equal(t, arg.Balance, account.Balance)
require.Equal(t, arg.Currency, account.Currency)
// 检查ID是否自动生成的,必须不为0
require.NotZero(t, account.ID)
require.NotZero(t, account.CreatedAt)
}
run testoknavicataccountsRun package testsaccount.sql.go
2. 生成测试数据
张三
通过生成随机数据,我们将节省大量的时间来确定要使用的值,代码也会更简洁易懂,并且由于数据是随机的,它将帮我们避免多个单元测试之间的冲突,比如,数据库中某个字段有唯一约束。
utilrandom.gopackage util
init()rand.Seed()time.Now().UnixNano()
package util
import (
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
RandomInt
func RandomInt(min, max int64) int64 {
return min + rand.Int63n(max-min+1)
}
接下来,再编写一个生成随机字符串的函数,为此,需要声明一个包含所有字符串的字母表,简单起见,只用了26个小写字母:
var alphabet = "abcdefghijklmopqrstuvwxyz"
func RandomString(n int) string {
var sb strings.Builder
k := len(alphabet)
for i := 0; i < n; i++ {
c := alphabet[rand.Intn(k)]
sb.WriteByte(c)
}
return sb.String()
}
随机生成中文的姓名
func RandomOwner() string {
return RandomString(6)
}
同样,定义一个生成随机金额的函数,假设它是0到1000的整数
func RandomMoney() int64 {
return RandomInt(0, 1000)
}
"RMB", "USD", "EUR", "CAD"
func RandomCurrency() string {
currencies := []string{"RMB", "USD", "EUR", "CAD"}
n := len(currencies)
return currencies[rand.Intn(n)]
}
random.go
package util
import (
"math/rand"
"strings"
"time"
)
var alphabet = "abcdefghijklmopqrstuvwxyz"
func init() {
rand.Seed(time.Now().UnixNano())
}
/**
* 生成随机整数
*/
func RandomInt(min, max int64) int64 {
return min + rand.Int63n(max-min+1)
}
/**
* 生成随机字符串
*/
func RandomString(n int) string {
var sb strings.Builder
k := len(alphabet)
for i := 0; i < n; i++ {
c := alphabet[rand.Intn(k)]
sb.WriteByte(c)
}
return sb.String()
}
/**
* 随机生成账户所有者
*/
func RandomOwner() string {
return RandomString(6)
}
/**
* 随机生成金额
*/
func RandomMoney() int64 {
return RandomInt(0, 1000)
}
/**
* 随机生成币种
*/
func RandomCurrency() string {
currencies := []string{"RMB", "USD", "EUR", "CAD"}
n := len(currencies)
return currencies[rand.Intn(n)]
}
account_test.go
"张三"util.RandomOwner()100util.RandomMoney()RMButil.RandomCurrency()
arg := CreateAccountParams{
Owner: util.RandomOwner(),
Balance: util.RandomMoney(),
Currency: util.RandomCurrency(),
}
run testnavicat
Makefile
test:
go test -v -cover ./...
-v-cover./...Makefile
postgres:
docker run --name postgres14 -e POSTGRES_PASSWORD=123456 -e POSTGRES_USER=root -p 5432:5432 -d postgres:14-alpine
createdb:
docker exec -it postgres14 createdb --username=root --owner=root simple_bank
dropdb:
docker exec -it postgres14 dropdb simple_bank
migrateup:
migrate --path db/migration --database="postgresql://root:123456@localhost:5432/simple_bank?sslmode=disable" -verbose up
migratedown:
migrate --path db/migration --database="postgresql://root:123456@localhost:5432/simple_bank?sslmode=disable" -verbose down
sqlc:
sqlc generate
test:
go test -v -cover ./...
.PHONY: postgres, createdb, dropdb, migrateup, migratedown, sqlc, test
来到项目终端,运行:
make test
可以看到,运行完成测试时打印出了详细的日志。
make test-count=1go test -v -cover ./... -count=1
CRUD
GetAccountaccount_test.goTestGetAccountCRUDAccount
AccountAccount
把之前的代码重构一下,如下:
package db
import (
"context"
"simplebank/util"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func createRandomAccount(t *testing.T) Account {
arg := CreateAccountParams{
Owner: util.RandomOwner(),
Balance: util.RandomMoney(),
Currency: util.RandomCurrency(),
}
account, err := testQueries.CreateAccount(context.Background(), arg)
// err 必须为 nil
require.NoError(t, err)
// account 不能为空对象
require.NotEmpty(t, account)
// 账户的所有者、余额和币种是否与输入的一致
require.Equal(t, arg.Owner, account.Owner)
require.Equal(t, arg.Balance, account.Balance)
require.Equal(t, arg.Currency, account.Currency)
// 检查ID是否自动生成的,必须不为0
require.NotZero(t, account.ID)
require.NotZero(t, account.CreatedAt)
return account
}
func TestCreateAccount(t *testing.T) {
createRandomAccount(t)
}
func TestGetAccount(t *testing.T) {
account1 := createRandomAccount(t)
// 查询 account, 参数为 account1 的 id,把结果给 account2
account2, err := testQueries.GetAccount(context.Background(), account1.ID)
// 这里应该没错误
require.NoError(t, err)
// account2 也必须不是空的
require.NotEmpty(t, account2)
// account2 的所有字段的值应该和 account1 所有字段的值相同
require.Equal(t, account2.ID, account1.ID)
require.Equal(t, account2.Owner, account1.Owner)
require.Equal(t, account2.Balance, account1.Balance)
require.Equal(t, account2.Currency, account1.Currency)
require.WithinDuration(t, account1.CreatedAt, account2.CreatedAt, time.Second)
}
TestGetAccountrun test
TestUpdateAccount()
account1 := createRandomAccount(t)
然后,定义参数,如下:
func TestUpdateAccount(t *testing.T) {
account1 := createRandomAccount(t)
arg := UpdateAccountParams{
ID: account1.ID,
Balance: util.RandomMoney(),
}
account2, err := testQueries.UpdateAccount(context.Background(), arg)
require.NoError(t, err)
require.NotEmpty(t, account2)
// 比较 account2 和 account1, 除了 Balance,其他的字段都应该相同
require.Equal(t, account2.ID, account1.ID)
require.Equal(t, account2.Owner, account1.Owner)
// 这里使用 arg.Balance 和 account2.Balance 比较
require.Equal(t, account2.Balance, arg.Balance)
require.Equal(t, account2.Currency, account1.Currency)
require.WithinDuration(t, account1.CreatedAt, account2.CreatedAt, time.Second)
}
再次运行这个函数的单元测试,可以看到,也测试通过了!
TestDeleteAccount
func TestDeleteAccount(t *testing.T) {
account1 := createRandomAccount(t)
err := testQueries.DeleteAccount(context.Background(), account1.ID)
require.NoError(t, err)
// 为了验证账户确实被删除了,再查找一次
account2, err := testQueries.GetAccount(context.Background(), account1.ID)
// 因为已经删除掉了,这里必须有错误
require.Error(t, err)
// 更准确的说,错误应该是 sql.ErrNoRows
require.EqualError(t, err, sql.ErrNoRows.Error())
// account2 也应该是空的
require.Empty(t, account2)
}
run test
ListAccounts
func TestListAccounts(t *testing.T) {
for i := 0; i < 10; i++ {
createRandomAccount(t)
}
arg := ListAccountsParams{
Limit: 5,
Offset: 5,
}
accounts, err := testQueries.ListAccounts(context.Background(), arg)
require.NoError(t, err)
// accounts 切片的长度为 5
require.Len(t, accounts, 5)
// 变量 accounts, 其中的每个 account 都不能为空
for _, account := range accounts {
require.NotEmpty(t, account)
}
}
run test
run package testsaccount.sql.goaccount_test.go
package db
import (
"context"
"database/sql"
"simplebank/util"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func createRandomAccount(t *testing.T) Account {
arg := CreateAccountParams{
Owner: util.RandomOwner(),
Balance: util.RandomMoney(),
Currency: util.RandomCurrency(),
}
account, err := testQueries.CreateAccount(context.Background(), arg)
// err 必须为 nil
require.NoError(t, err)
// account 不能为空对象
require.NotEmpty(t, account)
// 账户的所有者、余额和币种是否与输入的一致
require.Equal(t, arg.Owner, account.Owner)
require.Equal(t, arg.Balance, account.Balance)
require.Equal(t, arg.Currency, account.Currency)
// 检查ID是否自动生成的,必须不为0
require.NotZero(t, account.ID)
require.NotZero(t, account.CreatedAt)
return account
}
func TestCreateAccount(t *testing.T) {
createRandomAccount(t)
}
func TestGetAccount(t *testing.T) {
account1 := createRandomAccount(t)
// 查询 account, 参数为 account1 的 id,把结果给 account2
account2, err := testQueries.GetAccount(context.Background(), account1.ID)
// 这里应该没错误
require.NoError(t, err)
// account2 也必须不是空的
require.NotEmpty(t, account2)
// account2 的所有字段的值应该和 account1 所有字段的值相同
require.Equal(t, account2.ID, account1.ID)
require.Equal(t, account2.Owner, account1.Owner)
require.Equal(t, account2.Balance, account1.Balance)
require.Equal(t, account2.Currency, account1.Currency)
require.WithinDuration(t, account1.CreatedAt, account2.CreatedAt, time.Second)
}
func TestUpdateAccount(t *testing.T) {
account1 := createRandomAccount(t)
arg := UpdateAccountParams{
ID: account1.ID,
Balance: util.RandomMoney(),
}
account2, err := testQueries.UpdateAccount(context.Background(), arg)
require.NoError(t, err)
require.NotEmpty(t, account2)
// 比较 account2 和 account1, 除了 Balance,其他的字段都应该相同
require.Equal(t, account2.ID, account1.ID)
require.Equal(t, account2.Owner, account1.Owner)
// 这里使用 arg.Balance 和 account2.Balance 比较
require.Equal(t, account2.Balance, arg.Balance)
require.Equal(t, account2.Currency, account1.Currency)
require.WithinDuration(t, account1.CreatedAt, account2.CreatedAt, time.Second)
}
func TestDeleteAccount(t *testing.T) {
account1 := createRandomAccount(t)
err := testQueries.DeleteAccount(context.Background(), account1.ID)
require.NoError(t, err)
// 为了验证账户确实被删除了,再查找一次
account2, err := testQueries.GetAccount(context.Background(), account1.ID)
// 因为已经删除掉了,这里必须有错误
require.Error(t, err)
// 更准确的说,错误应该是 sql.ErrNoRows
require.EqualError(t, err, sql.ErrNoRows.Error())
// account2 也应该是空的
require.Empty(t, account2)
}
func TestListAccounts(t *testing.T) {
for i := 0; i < 10; i++ {
createRandomAccount(t)
}
arg := ListAccountsParams{
Limit: 5,
Offset: 5,
}
accounts, err := testQueries.ListAccounts(context.Background(), arg)
require.NoError(t, err)
// accounts 切片的长度为 5
require.Len(t, accounts, 5)
// 变量 accounts, 其中的每个 account 都不能为空
for _, account := range accounts {
require.NotEmpty(t, account)
}
}
4. 随机生成中文姓名的测试数据
Owner
random.go
var lastNames = []string{"李", "王", "张", "刘", "陈", "杨", "黄", "赵", "周", "吴", "徐", "孙", "朱", "马", "胡", "郭", "林", "何", "高", "梁", "郑", "罗", "宋", "谢", "唐", "韩", "曹", "许", "邓", "萧", "冯", "曾", "程", "蔡", "彭", "潘", "袁", "於", "董", "余", "苏", "叶", "吕", "魏", "蒋", "田", "杜", "丁", "沈", "姜", "范", "江", "傅", "钟", "卢", "汪", "戴", "崔", "任", "陆", "廖", "姚", "方", "金", "邱", "夏", "谭", "韦", "贾", "邹", "石", "熊", "孟", "秦", "阎", "薛", "侯", "雷", "白", "龙", "段", "郝", "孔", "邵", "史", "毛", "常", "万", "顾", "赖", "武", "康", "贺", "严", "尹", "钱", "施", "牛", "洪", "龚"}
var maleNames = []string{"豪", "言", "玉", "意", "泽", "彦", "轩", "景", "正", "程", "诚", "宇", "澄", "安", "青", "泽", "轩", "旭", "恒", "思", "宇", "嘉", "宏", "皓", "成", "宇", "轩", "玮", "桦", "宇", "达", "韵", "磊", "泽", "博", "昌", "信", "彤", "逸", "柏", "新", "劲", "鸿", "文", "恩", "远", "翰", "圣", "哲", "家", "林", "景", "行", "律", "本", "乐", "康", "昊", "宇", "麦", "冬", "景", "武", "茂", "才", "军", "林", "茂", "飞", "昊", "明", "明", "天", "伦", "峰", "志", "辰", "亦"}
var femaleNames = []string{"佳", "彤", "自", "怡", "颖", "宸", "雅", "微", "羽", "馨", "思", "纾", "欣", "元", "凡", "晴", "玥", "宁", "佳", "蕾", "桑", "妍", "萱", "宛", "欣", "灵", "烟", "文", "柏", "艺", "以", "如", "雪", "璐", "言", "婷", "青", "安", "昕", "淑", "雅", "颖", "云", "艺", "忻", "梓", "江", "丽", "梦", "雪", "沁", "思", "羽", "羽", "雅", "访", "烟", "萱", "忆", "慧", "娅", "茹", "嘉", "幻", "辰", "妍", "雨", "蕊", "欣", "芸", "亦"}
func RandomChineseFirstname(names []string, wordNum int64) string {
n := len(names)
var sb strings.Builder
for i := 1; i < int(wordNum); i++ {
sb.WriteString(names[rand.Intn(n)])
}
return sb.String()
}
/**
* 生成随机的中文姓名
*/
func RandomChineseOwner() string {
n := len(lastNames)
lastname := lastNames[rand.Intn(n)]
// 随机男女
gender := RandomInt(0, 1)
// 随机几个字的名,2个或3个
len := RandomInt(2, 3)
var firstname = ""
if gender == 0 {
firstname = RandomChineseFirstname(femaleNames, len)
} else {
firstname = RandomChineseFirstname(maleNames, len)
}
return lastname + firstname
}
account_test.goutil.RandomOwner()util.RandomChineseOwner()
arg := CreateAccountParams{
Owner: util.RandomChineseOwner(),
Balance: util.RandomMoney(),
Currency: util.RandomCurrency(),
}
make test