没有后端基础,学起来真是费劲,所以打算速刷一下,代码跟着敲一遍,有个印象,大项目肯定也做不了了,先把该学的学了,有空就跟点单体项目,还有该看的书....
目录
🍌单元测试
🌼assert
🌼覆盖率
🦊tips
🦊依赖
🦊文件处理
🍌Mock测试
🍌基准测试
🍉项目实战
🦂ER图
🦂分层结构
🦂组件工具
💪Repository
💪Service
💪Controller
💪Router
💤运行
🍌单元测试
规则
(1)测试文件以 _test.go 结尾
源代码与测试代码👇
(2)测试函数命名规范:func TestXxx(*testing.T) Test紧挨着第一个字母大写👇
(3)初始化逻辑放到TestMain()里
🌼assert
导入开源网站的assert包,来进行判等,不等等测试操作👇
单元测试的样例👇
judge.go
package judge
func JudgePassLine(score int16) bool {
if score >= 60 {
return true
}
return false
}
judge_test.go
package judge
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestJudgePassLineTrue(t *testing.T) {
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
Run一下👇
👇
👆解释
testing.T
是 Go 语言测试框架中的一个结构体类型,它提供了一组方法和属性用于管理和报告测试的结果。当你编写和运行测试函数时,测试框架会自动创建一个testing.T
类型的实例,并将其传递给测试函数
🌼覆盖率
当我们写了单元测试后👇
覆盖率越高,代码的质量越有保证,那如何查看覆盖率呢👇
PS:发现个Goland小技巧,ctrl + z可以返回上一步代码,误删了也不要紧
要查看覆盖率,首先要切到对应目录下,视频中是👇
但我只会先cd到对应目录,结果一样👇
覆盖率意味着👇
judge.go,函数中一共3行有效代码,但是judge_test.go传入的70,只会运行前2行,所以覆盖率是 2 / 3
当我们需要100%覆盖率,只需要增加一个测试分支👇
func TestJudgePassLineFail(t *testing.T) {
isPass := JudgePassLine(50)
assert.Equal(t, true, isPass)
}
👆通过不断地,对各个分支的测试,保证了测试的完备性, 减少了BUG的产生
🦊tips
🦊依赖
DB:数据库database
Cache:Redis类似的组件
File:本地文件
👆三项属于项目中的强依赖
在单元测试中,一般有2个目标:(1)幂等 (2)稳定
(1)幂等:多次重复一个case的测试,结果一样
(2)稳定:单元测试之间是相互隔离的,即任何时间 / 函数下,都能够运行
而需要保证幂等 / 稳定,需要Mock机制,下面先讲文件处理
🦊文件处理
log
line11
line22
line33
line44
line55
ProcessFirstLine.go
package firstLine
import (
"bufio"
"os"
"strings"
)
// ReadFirstLine 从文件中读取第一行内容
func ReadFirstLine() string {
open, err := os.Open("log") // 打开名为 "log" 的文件
defer open.Close() // 延迟关闭文件, 避免资源泄露
if err != nil {
return "" // 发生错误时返回空字符串
}
scanner := bufio.NewScanner(open) // 创建一个扫描器
for scanner.Scan() { // 循环遍历文件的每一行
return scanner.Text() // 返回第一行内容
}
return "" // 文件为空时返回空字符串
}
// ProcessFirstLine 处理第一行内容,将 "11" 替换为 "00"
func ProcessFirstLine() string {
line := ReadFirstLine() // 调用 ReadFirstLine 函数获取第一行内容
destLine := strings.ReplaceAll(line, "11", "00") // 将 "11" 替换为 "00"
return destLine // 返回替换后的结果
}
ProcessFirstLine_test.go
package firstLine
import (
"github.com/stretchr/testify/assert"
"testing"
)
// TestProcessFirstLine 是对 ProcessFirstLine 函数的单元测试
func TestProcessFirstLine(t *testing.T) {
firstLine := ProcessFirstLine() // 调用 ProcessFirstLine 函数
assert.Equal(t, "line00", firstLine) // 使用断言验证结果是否符合预期
}
equal还是报错,但是点击这里还是可以PASS👇
解释
(1)
bufio:高效读取数据的包
bufio.NewReader()
:创建一个新的Reader
对象,用于读取数据。它接收一个io.Reader
类型的参数,并使用默认大小的缓冲区bufio.NewScanner()
:创建一个新的Scanner
对象,用于对输入流进行扫描。Scanner
可用于逐行读取文本等Reader
类型:它提供了各种方法来从输入流中读取数据,如ReadString()
用于读取字符串,ReadBytes()
用于读取字节切片等Scanner
类型:它提供了各种方法来解析输入流中的数据,如Scan()
用于逐行读取文本,ScanBytes()
用于逐个字节读取等Scanner.Text()
:返回当前扫描的文本内容Scanner.Scan()
:将扫描器移动到下一行,并返回是否还有更多行可供扫描(2)
os
是一个提供与操作系统相关功能的包
os.Args
:一个字符串切片,包含命令行参数os.Exit(code int)
:终止当前程序的执行,并返回给定的错误码os.Getwd()
:返回当前的工作目录的路径名os.Chdir(dir string)
:将当前的工作目录更改为指定的目录os.Mkdir(name string, perm FileMode) error
:创建一个新目录os.Open(name string) (*File, error)
:打开一个文件用于读取os.Create(name string) (*File, error)
:创建一个文件用于写入os.Remove(name string) error
:删除指定的文件或目录os.Rename(oldname, newname string) error
:重命名(移动)文件或目录os.Stdout
、os.Stdin
、os.Stderr
:标准输出、标准输入和标准错误输出的文件对象
但是,如果源文件被人篡改,那么测试文件,在特定场景下,也就无法运行,那如何Mock呢?👇
🍌Mock测试
开源Mock测试包 -- bouk/monkey: Monkey patching in Go (github.com)
打桩,即用函数A替换函数B👇
不得不吐槽一下,内部课贴的代码只贴部分,默认剩下部分你都会了,没学过也不能无中生有吧...真的觉得,每一点代码都力求自己得到对应输出,有点浪费时间了,先速通吧...
🍌基准测试
看了一遍...
🍉项目实战
描述
用例
👆用户浏览页面,主要展示2方面内容,一是Topic话题,一是PostList回帖的列表
🦂ER图
Entity Relationship Diagram
ER图由以下三个主要组成部分构成:实体,属性,关系。
🦂分层结构
数据层:数据Model,外部数据的增删改查
逻辑层:业务Entity
视图层:视图view,处理和外部的交互逻辑
🦂组件工具
(1)Gin高性能 go web 框架
gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin. (github.com)
(2)Go mod
go mod init
go get gopkg.in/gin-gonic/gin.v1@v1.3.0 --> 执行后,go.mod就有gin的依赖了
💪Repository
index
两个索引👇
初始化话题数据索引👇
package main //入口
import (
"bufio" //缓冲读取
"encoding/json" //处理JSON数据
"os" //操作系统交互
)
// 初始化主题索引映射
func initTopicIndexMap(filePath string) error {
open, err := os.Open(filePath + "topic") //打开文件filePath
if err != nil { //打开错误
return err
}
scanner := bufio.NewScanner(open) //逐行扫描的工具
topicTmpMap := make(map[int64]*Topic) //值是指向Topic结构体的指针
for scanner.Scan() {
text := scanner.Text() //保存每行内容
var topic Topic
if err := json.Unmarshal([]byte(text), &topic); err != nil {
return err //json.Unmarshal将text解析为Topic结构体
}
topicTmpMap[topic.Id] = &topic //保存解析的结构体保
}
topicIndexMap = topicTmpMap //映射赋值给全局变量
return nil
}
查询
👇
package main
import (
"bufio" //缓冲读取
"encoding/json" //处理JSON数据
"os" //操作系统交互
"sync"
)
// 声明一个全局变量 topicIndexMap,用于存储主题索引映射
var topicIndexMap map[int64]*Topic
// Topic 定义 Topic 结构体,包含了主题的属性
type Topic struct {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
CreateTime int64 `json:"create_time"`
}
// TopicDao 定义 TopicDao 结构体
type TopicDao struct {
}
var (
topicDao *TopicDao
topicOnce sync.Once
)
// NewTopicDaoInstance 函数返回一个 TopicDao 实例
func NewTopicDaoInstance() *TopicDao {
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
// QueryTopicById 方法根据 id 查询并返回对应的 Topic 实例
func (*TopicDao) QueryTopicById(id int64) *Topic {
return topicIndexMap[id]
}
// 初始化主题索引映射
func initTopicIndexMap(filePath string) error {
open, err := os.Open(filePath + "topic") //打开文件filePath
if err != nil { //打开错误
return err
}
scanner := bufio.NewScanner(open) //逐行扫描的工具
topicTmpMap := make(map[int64]*Topic) //值是指向Topic结构体的指针
for scanner.Scan() {
text := scanner.Text() //保存每行内容
var topic Topic
if err := json.Unmarshal([]byte(text), &topic); err != nil {
return err //json.Unmarshal将text解析为Topic结构体
}
topicTmpMap[topic.Id] = &topic //保存解析的结构体保
}
topicIndexMap = topicTmpMap //映射赋值给全局变量
return nil
}
💪Service
定义两个实体👇
流程👇
流程代码👇
//Do 方法执行查询页面信息的流程,返回 PageInfo 实例和错误信息
func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
if err := f.checkParam(); err != nil { //参数校验
return nil, err //校验失败
}
if err := f.prepareInfo(); err != nil { //准备数据
return nil, err
}
if err := f.packPageInfo(); err != nil { //组装实体
return nil, err
}
return f.pageInfo, nil //返回 PageInfo 实例和 nil,操作成功
}
💪Controller
....看了一遍....
💪Router
💤运行
end....