单元测试
什么是单元测试
- 首先,它是测试
- 其次,它是单元测试
- 举个栗子
实现一个加法函数
func Add(x, y int) int {
return x + y
}
要测试函数是否正确,我们可能会写下这坨代码
func TestAdd() {
type TCase struct {
x, y, ret int
}
cases := make([]TCase, 0, 0)
// 第一个 Case
cases = append(cases, TCase{
x: 1,
y: 2,
ret: 3,
})
// 第二个 Case
cases = append(cases, TCase{
x: -1,
y: 1,
ret: 0,
})
for _, c := range cases {
got := Add(c.x, c.y)
if got != c.ret {
log.Printf("%d+%d, got %d, but %d expected", c.x, c.y, got, c.ret)
}
}
}
为什么需要单元测试
- 函数绝对不会有 Bug,覆盖率 100% 了(正确性)
- 总有一天,你会遇到重构(重构)
- 钝角(其他难以言表)
哪里需要单元测试
- 我就加了一个 if,怎么就出 bug 了(复杂&&容易出错)
- 这个库整个公司都在使用,出问题就 n+1 了(基础代码、公共代码)
- 这个广告计费模块太重要了(重要性)
- 这个需求老变,每次一变我就要改好多单测(基础、底层)
什么时候写单元测试
- 宇宙的尽头是 TDD(编码前)
- 一步一个脚印(编码中)
- 偷偷补上,然后惊艳所有人(编码后)
如何写单元测试
在当前 package 下新建文件add.go
,并实现 Add
函数
// add.go
package main
func Add(x, y int) int {
return x + y
}
我们想测试 Add 函数,那么我们需要在 add.go
的 package 下创建 add_test.go
,并实现逻辑
// add_test.go
package main
func TestAdd1(t *testing.T) {
x := 1
y := 1
want := 2
got := Add(x, y)
if got != want {
t.Errorf("%d+%d=%d, but %d expected", x, y, got, want)
}
}
如果我们想并发跑很多 Case
// add_test.go
func TestAdd2(t *testing.T) {
type tCase struct {
x int
y int
expected int
}
cases := make([]*tCase, 0, 0)
// test1
cases = append(cases, &tCase{
x: 1,
y: -1,
expected: 0,
})
// test2
cases = append(cases, &tCase{
x: 1,
y: 2,
expected: 3,
})
for i, c := range cases {
t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) {
got := Add(c.x, c.y)
if got != c.expected {
t.Errorf("%d+%d expected %d, but %d got", c.x, c.y, c.expected, got)
}
})
}
}
mock 必不可少
// add_test.go
func init() {
rand.Seed(time.Now().UnixNano())
}
func AddWithRandom(x, y int) int {
return x + y + rand.Int()
}
mock random.Int()
func TestAddWithRandom(t *testing.T) {
monkey.Patch(rand.Int, func() int { return 0 })
x := 1
y := 1
expected := 2
got := AddWithRandom(x, y)
if got != expected {
t.Errorf("%d+%d expected %d, but %d got", x, y, expected, got)
}
}
基准测试
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
for j := 0; j < 1e9; j++ {
Add(i, i+1)
}
}
}
func BenchmarkMul(b *testing.B) {
for i := 0; i < b.N; i++ {
for j := 0; j < 1e9; j++ {
Mul(i, i+1)
}
}
}
Gin 框架
什么是框架
先举一个简单例子,比如小 A 让我实现 2^n % 123456789,我三下五除二搞定了
func main() {
var n int
fmt.Scanf("%d", &n)
mod := 123456789
x := 2
ans := 1
for ; n > 0; n >>= 1 {
if n&1 != 0 {
ans = ans * x % mod
}
x = x * x % mod
}
fmt.Printf("%d\n", ans)
}
(函数)过了几天,小 B 又让我实现 5^n % 6789,这时候我有两种方法,一种是 copy 代码改一改,一种是抽象一下,写一个 pow 函数,防止之后有小 C、 小 D 又有类似的需求
func pow(x, n, mod int) (ans int) {
ans = 1
for ; n > 0; n >>= 1 {
if n&1 != 0 {
ans = ans * x % mod
}
x = x * x % mod
}
return
}
(库)也不知道为什么,产品喜欢上了求幂,不仅给我提了需求,还给了研发小 E 提了类似的需求。小 E 以一顿饭的报酬想要拷贝我的代码,我怎么会同意这种做法,拷贝代码太低级了,我说:饭就不用了,我搞个库给你。
(框架)产品为了让大家更好的理解框架,提了一个惨绝人寰的需求,需求如下
- 输入一个整数 n(HTTP 请求)
- 斐波那契数列第 n 项为 x(建立 TCP 连接)
- y = n ^ x % 1993(解析 HTTP 协议)
- 根据 y 的特性,进行处理,这个处理是经常变化的(业务逻辑)
- 例如1:如果
y
是奇数,返回hellow
,如果y
是偶数,返回world
- 例如2:如果
y
大于1000
则巴拉巴拉,否则巴拉巴拉
- 例如1:如果
简单地理解
- 函数:代码复用
- 库:跨项目的代码复用
- 框架:本身也是一个库,可能由多个库组装而成,形成一个解决方案,开发者只需要关注业务逻辑,本质也是代码的复用
HTTP 协议
什么是协议
数据+约定
什么是 HTTP 协议
- 应用层协议
- HTTP Header
- Method:GET、POST、PUT、PATCH、DELETE
- Content-Type:application/json、text/html、multipart/form-data
- Status Code:2xx,3xx,4xx,5xx
- Cookie:
- Ajax
- Version 1.0、1.1、2.0、3.0
Gin 框架的使用
Path + Method -> Handler
func main() {
// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
// By default it serves on :8080 unless a
// PORT environment variable was defined.
router.Run()
// router.Run(":3000") for a hard coded port
}
Validation -> Binding
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.Default()
// Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding XML (
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <user>manu</user>
// <password>123</password>
// </root>)
router.POST("/loginXML", func(c *gin.Context) {
var xml Login
if err := c.ShouldBindXML(&xml); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if xml.User != "manu" || xml.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding a HTML form (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
Gorm 框架
什么是数据库
- 存储 + CURD
- 快:索引
什么是关系型数据库
举个例子,老师课表
CREATE TABLE `teacher_schedule` (
`id` INT UNSIGNED AUTO_INCREMENT COMMENT "ID",
`teacher_id` INT COMMENT "老师 ID",
`course_id` INT COMMENT "课程 ID",
`time` TIMESTAMP COMMENT "上课时间",
PRIMARY KEY(`id`),
KEY(`teacher_id`),
KEY(`course_id`)
);
CREATE TABLE `teacher` (
`id` INT UNSIGNED AUTO_INCREMENT COMMENT "ID",
`name` VARCHAR(255) COMMENT "老师姓名",
PRIMARY KEY(`id`)
);
CREATE TABLE `course` (
`id` INT UNSIGNED AUTO_INCREMENT COMMENT "ID",
`name` VARCHAR(255) COMMENT "课程名称",
PRIMARY KEY(`id`)
)
老师 ID | 老师姓名 |
---|---|
1 | 张三 |
2 | 李四 |
课程 ID | 课程名称 |
---|---|
1 | 语文 |
2 | 数据 |
时间 | 老师 ID | 课程 ID |
---|---|---|
1月25日早8点 | 1 | 1 |
1月25日早10点 | 2 | 1 |
1月25日中午14点 | 1 | 2 |
什么是索引
各种数据结构,就是为了更快地检索到数据
- 查找树:B+、B
- Hash
什么是 ORM
- Struct -> Table
- Object -> Raw
- Filed -> Column
Gorm 的使用
Declaring Models
type Teacher struct {
ID int
Name string
}
func (Teacher) TableName() string {
return "teacher"
}
type Course struct {
ID int
Name string
}
func (Course) TableName() string {
return "course"
}
type TeacherSchedule struct {
ID int
TeacherID int
CourseID int
}
func (TeacherSchedule) TableName() string {
return "teacher_schedule"
}
Connect
package main
import (
"fmt"
"testing"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "root:bytedancecamp@tcp(180.184.74.182:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic(fmt.Sprintf("open mysql failed, err is %s", err))
}
// do something with db
}
CURD(Create、Update、Read、Delete)
func main() {
dsn := "root:bytedancecamp@tcp(180.184.74.182:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn))
db = db.Debug()
if err != nil {
panic(fmt.Sprintf("open mysql failed, err is %s", err))
}
// create
teacher := &Teacher{
Name: "张三",
}
if err := db.Create(teacher).Error; err != nil {
panic(fmt.Sprintf("create failed, err is %s", err))
}
// update
teacher.Name = "李四"
if err := db.Where("name = ?", "张三").Updates(teacher).Error; err != nil {
panic(fmt.Sprintf("update failed, err is %s", err))
}
// query
teacher2 := new(Teacher)
if err := db.First(teacher2, "name = ?", "李四").Error; err != nil {
panic(fmt.Sprintf("query failed ,err is %s", err))
}
// delete
if err := db.Delete(&Teacher{}, "name = ?", "李四").Error; err != nil {
panic(fmt.Sprintf("delete failed, err is %s", err))
}
}
[176.107ms] [rows:1] INSERT INTO `teacher` (`name`) VALUES ('张三')
[173.249ms] [rows:1] UPDATE `teacher` SET `name`='李四' WHERE name = '张三' AND `id` = 2
[86.831ms] [rows:1] SELECT * FROM `teacher` WHERE name = '李四' ORDER BY `teacher`.`id` LIMIT 1
[173.992ms] [rows:1] DELETE FROM `teacher` WHERE name = '李四'
软删除
初识微服务
什么是微服务
- 单体 -> 微服务,分工
- RPC
为什么需要微服务
- 效率、分工、沟通成本、交易成本、人月神话
- 康威定律
- 领域驱动设计
- 部署、复杂度
服务治理
- 服务注册、发现
- 限流、熔断
- 日志采集
- 链路追踪
最后一次更新于2022-01-29
0 条评论