这是我参与「第五届青训营 」笔记创作活动的第2天
重点内容
- 并行 VS 并发
- 依赖管理
- 测试
知识点介绍
-
Goroutine
协程:用户态,轻量级线程,栈MB级别
线程:内核态,线程跑多个协程,栈KB级别
快速 打印hello goroutine :0 ~ hello goroutine : 4
func hello(i int){
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine(){
for i := 0; i < 5; i++{
go func(j int){
hello(j)
}(i)
}
// 保证主协程不退出
time.Sleep(time.Second)
}
// 结果为乱序输出
// 说明并行
-
CSP (Communicating Sequential Processes)
提倡通过 通信共享内存 而不是通过共享内存实现通信
-
Channel
make(chan 元素类型,[缓冲大小])
// 无缓冲通道 也被称为同步通道
make(chan int)
// 有缓冲通道
make(chan int,2)
- 并发安全Lock
var (
x int64
lock sync.Mutex
)
func addWithLock(){
for i:= 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock(){
for i:= 0; i < 2000; i++ {
x += 1
}
}
- WaitGroup
func hello(i int){
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine(){
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++{
go func(j int){
defer Wg.Done()
hello(j)
}(i)
}
// 保证主协程不退出
wg.Wait()
}
-
Go Module
通过go.mod 文件管理依赖包版本
通过go get/go mod 指令工具管理依赖包
-
依赖管理三要素
配置文件,描述依赖 go.mod
中心仓库管理依赖库 Proxy
本地工具 go get/mod
-
go mod
init 初始化,创建go.mod文件 download 下载模块到本地缓存 tidy 增加需要的依赖,删除不需要的依赖 -
测试
回归测试(手动、终端回归固定场景
集成测试(自动化回归测试,集成一个功能维度
单元测试(开发阶段对模块进行测试
-
单元测试规则
所有测试文件都以_test.go结尾
func TestXxx(t *testing.T)
初始化逻辑放入TestMain中
func TestMain(m *testing.M){ // 测试前:数据装载、配置初始化等前置工作 code := m.Run() // 测试后:释放资源等收尾工作 os.Exit(code) } -
单元测试通过覆盖率判断是否完善
go test Xxx_test.go Xxx.go --cover一般覆盖率:50%~60%,较高覆盖率80%+
测试分支相互独立、全面覆盖
测试单元粒度足够小函数单一职责
-
Mock
为一个函数打桩
// Patch replaces a fucntion with another
func Patch(target, replacement interface{}) *PatchGuard {
t := reflect.ValueOf(target)
r := reflect.ValueOf(replacement)
patchValue(t,r)
return &PatchGuard{t,r}
}
// Unpatch removes any monkey patches on target
// returns whether target was patched in the first place
func Unpatch(target interface{}) bool {
return unpatchValue(reflect.ValueOf(target))
}
monkey.Patch(ReadFirstLine, func() string {
return "line110"
}) // 对ReadFirstLine函数进行打桩使其一直返回line110
-
分层结构
数据层(Repository):数据Model,外部数据的增删改查
逻辑层(Service):业务Entity,处理核心业务逻辑输出
视图层(Controller):视图View,处理和外部的交互逻辑
实践
社区话题页面
- 展示话题(标题,文字描述)和回帖列表
- 暂不考虑前端页面实现,仅仅实现一个本地web服务
- 话题和回帖数据用文件存储
课后总结
主要是通过展示话题项目,对开发流程的简单介绍。
作业
Handler
func PublishTopic(uidStr, title, content string) *PageData {
uid, _ := strconv.ParseInt(uidStr, 10, 64)
topicId, err := service.PublishTopic(uid, title, content)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code:0,
Msg: "success",
Data:map[string]int64{
"topic_id":topicId,
},
}
}
Route
r.POST("/community/topic/do", func(c *gin.Context) {
uid, _ := c.GetPostForm("uid")
title, _ := c.GetPostForm("title")
content, _ := c.GetPostForm("content")
data := handler.PublishTopic(uid, title, content)
c.JSON(200, data)
})
Repository
func (*TopicDao) CreateTopic(topic *Topic) error {
if err := db.Create(topic).Error; err != nil {
util.Logger.Error("insert topic err:" + err.Error())
return err
}
return nil
}
Service
publish_topic
func PublishTopic(userId int64, title, content string) (int64 error) {
return NewPublishTopicFlow(userId, title, content).Do()
}
func NewPublishTopicFlow(userId int64, title, content string) *PublishTopicFlow {
return &PublishTopicFlow{
userId: userId,
title: title,
content: content,
}
}
type PublishTopicFlow struct {
userId int64
title string
content string
topicId int64
}
func (f *PublishTopicFlow) Do() (int64, error) {
if err := f.checkParam(); err != nil {
return 0, err
}
if err := f.publish(); err != nil {
return 0, err
}
return f.topicId, nil
}
func (f *PublishTopicFlow) checkParam() error {
if f.userId <= 0 {
return errors.New("userId id must be larger than 0")
}
if utf8.RuneCountInString(f.title) >= 30 {
return errors.New("title length must be less than 30")
}
if utf8.RuneCountInString(f.content) >= 500 {
return errors.New("content length must be less than 500")
}
return nil
}
func (f *PublishTopicFlow) publish() error {
topic := &repository.Topic{
UserId: f.userId,
Title: f.title,
Content: f.content,
CreateTime: time.Now(),
}
if err := repository.NewTopicDaoInstance().CreateTopic(topic); err != nil {
return err
}
f.topicId = topic.Id
return nil
}
publish_topic_test
func TestPublishTopic(t *testing.T) {
// 生成超出长度的字符串进行测试
errString1 := getFixedLenString("测试", 40, 't')
errString2 := getFixedLenString("测试", 550, 't')
type args struct {
userId int64
title string
content string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "测试发布帖子1",
args: args{
userId: 1,
title: "用户1测试发布帖子",
content: "用户1测试发布帖子",
},
wantErr: false,
},
{
name: "测试发布帖子2",
args: args{
userId: 0,
title: "用户1测试发布帖子",
content: "用户1测试发布帖子",
},
wantErr: true,
},
{
name: "测试发布帖子3",
args: args{
userId: 1,
title: errString1,
content: "用户1测试发布帖子",
},
wantErr: true,
},
{
name: "测试发布帖子4",
args: args{
userId: 1,
title: "用户1测试发布帖子",
content: errString2,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := PublishTopic(tt.args.userId, tt.args.title, tt.args.content)
if (err != nil) != tt.wantErr {
t.Errorf("PublishTopic() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func getFixedLenString(str string, length int, char byte) string {
if len(str) == 0 {
return ""
}
if len(str) == length {
return str
}
//超出切后面
if len(str) > length {
return string(str[:length])
}
//缺少添加char
if len(str) < length {
slice := make([]byte, length-len(str))
for k := range slice {
slice[k] = char
}
return string(append(slice, []byte(str)...))
}
return ""
}