字节后端训练营笔记(二)

单元测试

什么是单元测试?

  • 首先它是测试==》检测代码正确性

  • 其次它是个单元==》粒度最小

  • 例如,实现一个加法函数:

  • 我们的几个单元测试可以这样进行:

    // add.go​
    package main​
    
    func Add(x, y int) int {​
       return x + y​
    }
// 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)​
   }​
}

为什么我们需要单元测试?

  • 万一有bug就完蛋了,测边界值防止意外发生,保证正确性

  • 万一你的函数要被重构了,新的函数怎么证明它的正确性,怎么debug

  • 钝角

哪里需要单元测试?

  • 我就加了一个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框架

  • 什么是框架?==》用库帮你做你不想写的部分,让你省出精力来写业务

  • 框架:由多个库组成,形成针对某一部分的解决方案《==》不断对代码的抽象和复用

  • 简单地理解

    • 函数:代码复用

    • 库:跨项目的代码复用

    • 框架:本身也是一个库,可能由多个库组装而成,形成一个解决方案,开发者只需要关注业务逻辑,本质也是代码的复用

  • Gin框架:HTTP的Web框架:帮你处理web相关的事情:路由解析、TCP建立、HTTP协议的解析等等,都帮你搞定了

  • 注:什么是协议:数据(传输的数据包)+约定(规则)

  • restful不属于一个协议,因为不是强制的,它只是一种良好的约束规则

  • 未登录跳转到统一登录界面去登录:使用302(临时重定向)(301一般用于path全部永久改动)

  • 一般浏览器遇到301会给你缓存,因为是永久改变的,不必再跳到原来界面,每次刷新都会直接跳到新界面,只有删除缓存再进入才有可能再经历一次301界面;而302是肯定不会给你缓存的

  • 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

// Binding from JSON
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补充

查找树用B和B+,不用二叉查找树,是为了更好地适配磁盘,减少I/O,从而避免层数过多。

B/B+的查找复杂度:logN

计算方法:

  • 如果只有一层,这层有无穷多个节点===》二分,logN;

  • 如果一层只有两个,但是有很多层===》变成平衡二叉树,logN;

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 = '李四'​

软删除:记录deleteAt,并不真的删除记录

微服务

什么是微服务

为什么需要微服务

  • 效率分工相关,人越多,沟通成本越高、分工越难、效率越低,总而言之就是人越少越好

  • 康威定律:公司的组织架构是怎样的,那么产品的结构就是怎么样的

  • 领域驱动设计(DDD):以对象为基础进行设计,将业务分割成小的部门

  • 单体架构部署起来会比微服务更困难和复杂。

服务治理

  • 需要负责服务注册、发现的中间件

  • 限流与熔断的流量解决方案

  • 日志采集

  • 链路追踪

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值