《流言》

一张图

感受

张爱玲一些早期的作品收录,读这本书不是因为是“张迷”。只是偶然间听说了张爱玲的事纪,“一生无后”最后在公寓里度过了余生自己打包好了房间里的所有东西干净的来干净的走。华春莹在外交上面的一次发言也是引用张爱玲的文章 “***是一袭华美的袍,爬满了蚤子”

《JavaScript权威指南》

JavaScript权威指南

感受

很厚,2-3月份读了快2个月。8月才写这个笔记,不是忘记了只是中间把它用起来了,具体就是写了2个工具站并且自认为很满意。这本书和所有教编程语言的书一样,中规中矩(词法,变量,表达式,流程语句,对象,函数,类模块,标准库,工业化应用)。对于我来说最大的收获就是ES6的一些新的语法特性和期约,因为这些是后面5个月有在高频使用到的。

我欠它一个幕布整理出来的总结

Tol.Vip-前端学习作业

前言

今年一直在断断续续的学习一些前端知识,满打满算5个多月了,这个项目算是对这些知识的应用。也是对这段时间读的3本书的一个总结。我计划的是有时间或者只要有了有趣的想法我就会去实现出来。
3本前端启蒙书

项目介绍

早上一到公司打开电脑后会打开一系列自己熟悉的网站。操作路径无非两种, 1.收藏夹 2.敲网址,这两种方式都有各自的弊端,收藏夹太多了不好管理,敲网址又太麻烦。 痛!!!

我有一个笔记本,一台台式,台式上有2个操作系统 win10和deepin。 Chrome在没用魔法的情况下没法同步收藏夹 痛痛!!!

我的收藏夹就是一股脑的往里面塞,找的时候经常要翻好几页,我就在想它就不能出一个搜索功能吗! 痛痛痛!!!

我很喜欢一款搜索工具叫Alfred,但是它只能在苹果下面用。deepin下我用uTools,他们都是优秀的能改变我使用习惯的工具。但是他们在快速打开一个网页上都没有形成闭环。 痛痛痛痛!!!


于是我就想做一个自己的网站,能够快速打开网页,能够搜索收藏,能够同步收藏,能够在不同的设备上使用。于是就有了这个项目。
它目前包含两部分,

  1. 网站: https://tol.vip/
  2. 扩展: 谷歌已上架 浏览器插件

插件弹出页面

插件弹出页面

快速收藏

快速收藏

T+L快捷键搜索

快速搜索

更多的界面不一一去截图

它更像一个工具站和导航站的结合体,我的愿景是把它做成一个工具导航站一个能改变用户使用习惯的产品。

项目技术栈

  • 前端 VUE3,NUXT3,ElementPlus,TypeScript,Plasmo
  • 后端 Go,Gin,Gorm,Gen
  • 数据库 MySQL
  • 鉴权 JWT
  • API管理 Swagger,Apifox
  • 发布部署 云效,Docker,PM2
  • CDN 又拍云,CF

收获

  • 学习到了前端一些浅薄的知识
  • Gen这个ORM生成的工具特别好用
  • 或许这是30岁以前唯一一个好意思给别人看一下的东西
  • 得到了快乐,花费的时间有了回报(精神上)

基于双塔模型的AI搜索工业实现

介绍

这篇文章只是介绍CLIP模型的工业实现,能力有限没法深入到具体的算法。目前了解到视觉中国的搜索就是用的这个模型实现的,可以去体验下效果。实现步骤相对简单,记录下来是因为这是最近接触到比较有趣的一个业务场景,同时也感叹下科技爆炸带来的惊叹。

前置知识

  • 双塔模型
    双塔模型如下图,指的是把图片和文字进行编码生成一个512维度的向量,然后比对图文向量得到相关的搜索图片。

  • 向量距离
    向量距离就是说的不同向量的相似度常见的距离算法有。
    1. 余弦相似度
    2. 欧几里得距离
    3. 曼哈顿距离

流程介绍

  1. 团队里面大多技术栈是 PHP + GO 加上环境安装需要解决一些依赖问题。所以把服务端进行了封装我放在了GitHub上 传送门
  2. 图上的ES-PUSH是之前写的一个es推送的小脚本,也是我的第一个Go程序mysql2elasticsearch没有向量类型开了副本提交了一版。
  3. PHP用gRPC的时候会很不顺畅不知道是我的问题还是普片现象,不过我也把grpc_php_plugin打包进了docker环境,如果有缘人遇到了类似的问题可以试试我的思路。

可以看这个dockerfile编译grpc_php_plugin 或者直接用编译好的镜像

1
registry.cn-shanghai.aliyuncs.com/ibaotu/php7.2

在这个环境里没法直接用protoc生成代码,(因为php docker的官方镜像太老了很多依赖包都出了问题)。 我是在PHP8里面调用编译好的grpc_php_plugin生成的。

  1. 再贴一下es的DSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
PUT /index_name
{
"mappings": {
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "text"
},
"vector": {
"type": "dense_vector",
"dims": 512
},
"picurl": {
"type": "keyword"
},
"c1": {
"type": "keyword"
}
}
}
}

POST index_name/_search
{
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"source": "cosineSimilarity(params.queryVector, 'vector') + 1.0",
"params": {
"queryVector": [
...
]
}
}
}
}

}

结尾

本来感觉有很多可以记录下来的,但是回头梳理了下大多耗时都是因为PHP的低版本造成的gRPC使用上的问题解决就行记录下环境编译的过程没什么意义 :-(

最后看下搜索了100w素材后的搜索结果

关键词:夏天

关键词: 沙滩上的美女

关键词: 吃饭的小孩

产考文献

https://modelscope.cn/models/damo/multi-modal_clip-vit-base-patch16_zh/summary

利用阿里云函数FC实现的抠图服务

总结

总结写在前面为了省流。

最后确定实施采用的付费API方案。原因是现在的开源模型在各场景下的表现效果没达到商用标准。但是整个流程是没问题的,展望现在AI的发展速度类似图片分割这类的模型开源也只是时间问题,到时候可以掏出这套流程直接部署线上。

可以后期关注 https://www.modelscope.cn/models/damo/cv_unet_universal-matting/summary

背景

我所在公司任职的项目组想开发抠图功能给用户提供免费的服务,其他项目组应用的第三方抠图服务成本20-30W一年,核心问题就是在抠图效果可接受的前提下需要缩减成本。团队也没有机器视觉处理和训练方面的经验。

模型选择

目前市面上几家巨头都有自己的AI社群,社群里面有训练好的模型并且可以在线体验。
脸书的 huggingface
阿里的 魔塔
百度的 飞浆
分别抽取了这几个社区图像分割热度最高的模型做测试

经过比对最后选定了阿里的damo/cv_swinL_panoptic-segmentation_cocopan模型

技术选型思考

  1. 机器学习首选Python,团队技术栈 PHP/GO为主
  2. 每家社区都有自己的框架,依赖项较多安装繁琐
  3. 网站日活10w用户,峰值qps1K

结合上述3点,我想的是

  1. gRPC对Python逻辑进行封装只处理抠图逻辑,client端调用处理业务逻辑(比如付费限流业务状态熔断等问题)统一开发语言
  2. 抠图服务做成无状态方便动态扩缩容
  3. 动态扩缩容可以选择k8s编排
  4. 考虑到k8s的维护成本和它在公司的普及率,换为阿里云函数

函数计算调研

场景

函数计算可以处理一小段的代码逻辑,触发方式分为事件触发HTTP请求,用于周期性的定时消费任务或者访问量不大的博客类网站有一定的价格优势。gRPC是基于HTTP2.0由此函数计算也兼容了gRPC调用。环境采用镜像的方式部署,代码可以接入各大托管平台。可以根据并发利用率阈值进行动态扩缩容。

目前来看解决了下面的问题

  1. 价格
  2. 容器编排
  3. 通信

计费

资源包 大小 金额(元)
vCPU 50w vCPU*秒 50
内存 100WGB*秒 10
调用函数包 1亿 80
流量 100GB 40
GPU 50wGB*秒 200

计费是多维度的比较玄幻公式是

1
2
vCPU资源使用费用=函数实例vCPU(vCPU)×执行时长(秒)×单价。
内存资源使用费用=函数实例内存(GB)×执行时长(秒)×单价。

实际使用中发现每次调用机器会悬停5-10分钟做为强制预热,期间也是按秒计费的。阿里提供了2中模式。

  1. 最后一次调用悬停10分钟按秒计费(调用1次也是会计费10分钟)
  2. 打开闲置计费强制永久1台机器为闲置状态费用是按秒计费的 1/10

肉眼评估下来用GPU服务任务控制在1s内价格是比直接买服务器来得划算的,并且可负载qps可长可短很鲁棒

DEMO

ProtoBuff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax="proto3";
option go_package ="../pb";
service ImgHandle {
rpc partition (Request) returns (Reply) {}
}

message Request {
string Url =1;
}


message Reply {
string jsonData = 1;
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from concurrent import futures
import grpc
import json
import pb.partition_pb2 as Impb
import pb.partition_pb2_grpc as IMrpc
import numpy as np

class NumpyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
return json.JSONEncoder.default(self, obj)

class ImgHandle(IMrpc.ImgHandleServicer):

def partition(self, request, context):
print(request.Url)
result = segmentor(request.Url)
s = json.dumps(result, cls=NumpyEncoder, ensure_ascii=False)

return Impb.Reply(jsonData=s)

def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))

IMrpc.add_ImgHandleServicer_to_server(ImgHandle(), server)
server.add_insecure_port('0.0.0.0:50051')
print("成功监听")
server.start()
server.wait_for_termination()
if __name__ == '__main__':

segmentor = pipeline(Tasks.image_segmentation, model='damo/cv_swinL_panoptic-segmentation_cocopan')
serve()


client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package main

import (
"bufio"
g "client/grpc"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"image"
_ "image/gif"
_ "image/jpeg"
"image/png"
_ "image/png"
"io"
"net/http"
"os"
"strconv"
"time"
)

const ADDR = "*****:8089"

type T struct {
Masks [][][]int `json:"masks"`
Labels []string `json:"labels"`
Scores []float64 `json:"scores"`
}



func main() {

cred := credentials.NewTLS(&tls.Config{
InsecureSkipVerify: false,
})
url := "https://pic.xxxx.com/01/92/25/82t888piCbj9.jpg"

conn, err := grpc.Dial(ADDR,
grpc.WithTransportCredentials(cred),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(1024*1024*1024*20)),
)

if err != nil {
fmt.Println(err)
}

client := g.NewImgHandleClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 50000*time.Second)
defer cancel()
fmt.Println("投递抠图任务")

reply, e := client.Partition(ctx, &g.Request{Url: url})

Down(url)

if e != nil {
fmt.Println(e)
}

var t = T{}
json.Unmarshal([]byte(reply.JsonData), &t)
fmt.Println("抠图处理完成,开始保存图片")
Img(t)

}

func Down(url string) {
client := http.DefaultClient
client.Timeout = 10 * time.Second
reps, err := client.Get(url)
if err != nil {
fmt.Println(err)
}
defer reps.Body.Close()
file, err := os.Create("./temp.png")
if err != nil {
fmt.Println(err)
}
defer file.Close()
w := bufio.NewWriter(file)
io.Copy(w, reps.Body)
w.Flush()
}

func Img(processing T) {

file, err := os.Open("./temp.png")
if err != nil {
panic(any(err))
}
defer file.Close()

atImg, _, e := image.Decode(file)

if e != nil {
fmt.Println(e)
}

wide := atImg.Bounds().Max.X
high := atImg.Bounds().Max.Y



for i, v := range processing.Labels {
resultImg := image.NewRGBA(image.Rect(0, 0, wide, high))
for w := 0; w < wide; w++ {
for h := 0; h < high; h++ {
isShow := processing.Masks[i][h][w]
if isShow == 1 {
resultImg.Set(w, h, atImg.At(w, h))
}
}
}
ImgSave(resultImg, v+"_"+strconv.Itoa(i))
}

}

func ImgSave(img *image.RGBA, name string) {
file, e := os.Create("./" + name + ".png")
if e != nil {
fmt.Println(e)
}
defer file.Close()
b := bufio.NewWriter(file)
defer b.Flush()
png.Encode(b, img)
}

Dockerfile

1
2
3
4
5
6
FROM registry.cn-hangzhou.aliyuncs.com/modelscope-repo/modelscope:ubuntu20.04-py37-torch1.11.0-tf1.15.5-1.2.0
EXPOSE 50051
RUN mkdir -p /usr/local/bin/service
COPY ./service /usr/local/bin/service
RUN python /usr/local/bin/service/docker_init.py #这里需要下载个800M的模型文件,提前打入image
CMD [ "python", "/usr/local/bin/service/main.py" ]

注意点

  1. 打包后的image放在阿里自家服务的同一大区构建会很快
    https://cr.console.aliyun.com/cn-shanghai/instances
  2. 云函数计算gRPC调用是固定端口8089,容器中的监听端口会自动映射上去
  3. 云函数URL触发没有鉴权机制,需要同步开通网关服务
  4. 建议使用相同区域的机器做下流服务通信走vpc,可以不做鉴权和节省流量成本

《设计模式之美》

图像 6

感受

这本书前前后后花了一个半月去阅读,国庆假在重庆休假刚好看完了最后一个设计模式,一句话总结是49买的书值了,并且对我来说时间没白花。

这本书分为2部分,第一部分:面向对象基础,和设计模式的原则 第二部分:设计模式的实现。

收获如下

  • 了解了单元测试的重要性 go的单元测试
  • 对面向对象更加清晰
  • 能理解之前看过一些源码作者的思路
- 大话设计模式 设计模式之美
优点 有UML更直观偏向设计模式的实现 没UML偏向设计模式的使用,有基础铺垫
劣势 场景没带入性 没前者学术

对面向对象与设计模式的看法

前言

写这篇日记的时候楼下的邻居正在装修,沪漂小伙苦不堪言。回想起之前朋友和我抱怨去杭州面试那里的面试官喜欢扣设计模式细节,和我自己3月亲身去重庆面试的经历,我发现很多的面试官真的很问这个并且抠细节,然而他们自己连面向对象是什么都很模糊。为问而问,为用而用,大可不必。

设计模式很重要

我写它的目的不是想证明设计模式不重要,也不是为了批判现在的航母面试。相反我是想佐证设计模式的重要性,但是在我们能真正有资格谈论设计模式前我们至少先对面向对象编程有个清晰的认知,对为什么设计出设计模式和设计的目的有个数。

面向对象

面向对象分为面向对象语言面向对象风格,我们常见的 PHP JAVA Go 等都是面向对象的语言,而 C Js 则不是面向对象语言。面向对象语言面向对象风格的区别是什么呢?

简单来说面向对象需要语言层提供下面的一些特性

面向对象语言的特性

继承

把可以复用的代码抽取出来放到父类提供复用,避免重复的写相同的代码,但是它也经常被诟病。深层级的继承会影响可测试性和可读性。由此产生了多组合少继承的说法,GO就是基于组合的设计思路。

封装

提供了信息隐藏和数据保护,我们只暴露出需要让调用方知道和修改的属性或者方法。

抽象

隐藏内部实现细节,调用方只需要知道调用一个方法是干什么的,而不需要明白是怎么做到的。常见的interfaceabstract是抽象实现的特性,而函数其实也是一种抽象

多态

可以用子类替换父类并可以调用子类的方法。

  • 支持父类对象引用子类对象
  • 支持继承
  • 支持子类重写父类

区别

面向对象语言 从语言层面支持了上述的特性,但是并不是支持了这些特性的语言就是面向对象编程的,因为它的特性可能被人为的破坏掉。
面向对象风格 是指的用面向过程的语言经过巧妙的设计也可以实现出面向对象语言所提供的特性

代码质量

面向过程编程和面向对象编程其实没有谁优于谁的说法,都是时代的产物。在代码量不多的场景下面向过程更适合阅读也可以更快的开发,在大型需要多人协作的场景下面向对象更适合。我们编写代码的时候需要考虑以下的一些衡量代码质量的标准。

可维护性

在不破话原有代码设计不引入新bug的情况下能快速修改或添加代码

可读性

任何人都可以编写计算机能理解的代码,而好的程序员能编写人能理解的代码。这是一个综合的评判比如良好的英文命名风格,模块设计等等。

可扩展性

不修改或者少量修改原有代码的情况下通过扩展的方式添加新的功能

灵活性

预留一定的扩展点,有足够的底层可复用抽象模块

简洁性

尽量保证代码简单减少不必要的炫技行为

可复用性

尽量减少重复代码的编写,复用以后代码

可测试性

可测试性侧面反映了代码质量的高低,比如我前面说到的go单元测试

设计模式的设计原则

设计模式其实只是一些具体的落地措施,实际上的一些设计思想前辈们是总结出了很多原则和套路的,最常见的有下面这些

单一职责

一个类或者模块只负责完成一个职责或者功能

里式代换

子类对象可以在任何地方替换父类对象

开放闭合

对扩展开放,对修改闭合

依赖反转

高层级模块不依赖低层级模块,应该通过抽象相互依赖,抽象不依赖细节,细节依赖抽象。实现依赖反转的场景方法有下面两个

控制反转(IOC)

指的把原来由程序控制的流程反转给了框架,类似Laravel我们只需要根据框架定义好的扩展点去写代码,流程会被框架以list的形式放着一起,一般还会暴露一些操作list的方法。如Laravel的服务提供者就是会把一系列相关的服务放到一个继承了同一个父类的list中暴露出了抽象方法。

依赖注入

在类中去实例化一个对象会造成类与类之间的耦合,这是违反我们设计原则的。依赖注入解决了类与类之间的依赖关系。由外部传入实例好的类对象,是一种基于接口编程的体现。同时依赖注入也为我们写单元测试mock提供了便利。

KISS

Keep It Simple and Stupid.
Keep It Short and Simple.
Keep It Simple and Straightforward
尽量保持简单

YAGNI

You Ain’t Gonna Need It。
不要设计当前用不到的功能

DRY

Don’t Repeat Yourself
不要编写重复代码

LoD

迪米特,高内聚低耦合,
高内聚 类本身的设计,相近的设计尽量放到同一个类中
低耦合 类之间的依赖关系要尽量的简单单一
每个模块只应该了解那些与它关系密切的模块(最少知道原则)

总结

学习坡度

学习设计模式是很有必要的,但是在这之前我们应该知道设计模式的由来,学习的路径建议是

了解面向对象 -> 读懂设计原则 -> 熟悉设计模式

而不是一味的去扣一些设计模式写法上面的细节

带着目的

要搞懂我们为什么要花这些时间去投资学习这些方法论,我的目的是

  1. 能更顺利的读懂框架源码,(Laravel我还想带入设计模式看一遍文档)了解设计思路
  2. 写出高质量的代码

go单元测试

契机

单元测试其实一直是我想做但是没有做的事,写php这么久其实也没怎么写过单测。我给自己找的借口是。

  1. 项目开发工期紧任务重
  2. 项目代码混乱现在已经没法写出单测代码

但是经历了以下的一些事我决定养成良好的写单测的习惯。

  1. 毛剑的 Open Go训练营反复强调了单测
  2. 在给耗子的megaease提pr的时候要求带上单测
  3. 看王峥的《设计模式之美》的时候又反复强调了测试性和重构后的单测

通过上面几件事我认识到想写出不被诟病的好代码除了有良好的 英文命名,优秀的工业设计外还需要覆盖率尽可能高的单元测试

go单元测试

go test 基础

go test 主要功能是提供 单元测试基准测试。我主要记录了单元测试

1
2
3
4
5
6
-bench regexp 执行相应的 benchmarks,例如 -bench=.;
-cover 开启测试覆盖率;
-run regexp 只运行 regexp 匹配的函数,例如 -run=Array 那么就执行包含有 Array 开头的函数;
-v 显示测试的详细命令。
-covermode=count 覆盖率
-coverprofile=count.out 覆盖率详情输出到文件

代码规范

  1. *_test.go 结尾
  2. 函数 Test* 开头

事例

代码

我写了一个用chromedp操作chrome的封装函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package browser

import (
"context"
"errors"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/cdproto/runtime"
"github.com/chromedp/chromedp"
"log"
"time"
)

type Browser struct {
ctx context.Context
Cancelfunc context.CancelFunc
}

func (b *Browser) NewBrowser() {
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("headless", true), // 开启窗口模式
chromedp.Flag("window-size", "1920,1080"),
)
b.ctx, b.Cancelfunc = chromedp.NewExecAllocator(context.Background(), opts...)
b.ctx, _ = chromedp.NewContext(b.ctx, chromedp.WithLogf(log.Printf))
}

func (b *Browser) RunGroup (url string , taks ...chromedp.Tasks) error{

actions := chromedp.Tasks{}
actions = append(actions, chromedp.Navigate(url))
for _,v := range taks {
actions = append(actions,v)
}

if err := chromedp.Run(
b.ctx,
actions,
); err != nil {
return err
}
return nil
}

func (b *Browser) Js(js string) chromedp.Tasks {
return chromedp.Tasks{
chromedp.ActionFunc(func(ctx context.Context) error {
_, exp, err := runtime.Evaluate(js).Do(ctx)
if err != nil {
return err
}
if exp != nil {
return exp
}
return nil
}),
}
}

func (b *Browser) SetCookie(host string, cookies ...string) chromedp.Tasks {
if len(cookies)%2 != 0 {
return chromedp.Tasks{
chromedp.ActionFunc(func(ctx context.Context) error {
return errors.New("length of cookies must be divisible by 2")
}),
}
}

return chromedp.Tasks{
chromedp.ActionFunc(func(ctx context.Context) error {
// create cookie expiration
expr := cdp.TimeSinceEpoch(time.Now().Add(180 * 24 * time.Hour))
// add cookies to chrome
for i := 0; i < len(cookies); i += 2 {
err := network.SetCookie(cookies[i], cookies[i+1]).
WithExpires(&expr).
WithDomain(host).
WithHTTPOnly(true).
Do(ctx)
if err != nil {
return err
}
}
return nil
}),
}
}

func (b *Browser) Screenshot(buff *[]byte, quality int) chromedp.Tasks{
return chromedp.Tasks{
chromedp.FullScreenshot(buff,quality),
}
}

然后写了一个测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package browser

import (
"github.com/stretchr/testify/assert"
"testing"
)


func TestBrowser(t *testing.T) {
assert := assert.New(t)

b := Browser{}

b.NewBrowser()
defer b.Cancelfunc()

url := "https://www.baidu.com"
e := b.RunGroup(url)
assert.Nil(e)
e = b.RunGroup("error url")
assert.NotNil(e)


e = b.RunGroup(url, b.Js("console.log(123)"))
assert.Nil(e)
e = b.RunGroup(url, b.Js("-----"))
assert.NotNil(e)

e = b.RunGroup(url, b.SetCookie(url,"zjj","123"))
assert.Nil(e)
e = b.RunGroup(url, b.SetCookie(url,"abc","123","111"))
assert.NotNil(e)

var buff []byte
e = b.RunGroup(url, b.Screenshot(&buff ,100))
assert.Nil(e)

if len(buff) <= 100 {
assert.Fail("image size err")
}

}

查看单测是否通过

覆盖率

查询覆盖率并且输出到文件做后续分析

1
2
go test chromedp*.go --covermode=count
go test chromedp*.go --coverprofile=count.out

1
go tool cover -func ./count.out

1
go tool cover -html ./count.out

相关类库

我个人认为这些库没必要每个都去学习而是用到再看,我了解一些常用的类库比如 testify和monkey考虑到篇幅有限而且它们文档已经很完善了,就没在这边做扩展。

名称 地址 作用
testify gihub assert高频使用,主要提供*testing.T的封装操作
mockery github Golang 的模拟代码自动生成器
httptest 文档 提供http的测试
go-sqlmock github Sql mock driver
miniredis github redis mock
monkey github 汇编语言重写可执行文件的打桩工具

基于阿里云发布案例

ps:除了用阿里用的codeup外其实GitHub支持也很完善,因为我并不打算把代码开源并且我一直用的云效的发布流程部署代码到服务器上,因此此我托管到了阿里code。GitHub 其实有 看见耗子哥用 https://docs.codecov.com 来做单测报告

新建仓库

创建忽略文件,关联远程分支,上传本地代码

新建流水线

在流水线中新建go的单元测试

测试命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apt-get update
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt-get install -y dpkg
dpkg -i ./google-chrome-stable_current_amd64.deb
apt-get install -f -y
dpkg -i ./google-chrome-stable_current_amd64.deb
# 默认使用goproxy.cn用户可手动调整
export GOPROXY=https://goproxy.cn
# 默认的单元测试命令
# 输出测试报告目录到当前工作目录,可自动上传并展示
mkdir -p golang-report
# 未使用Go Mod的用户需要打开一下注释
# export GOFLAGS=-mod=vendor
go test -v -json -cover -coverprofile cover.out ./... > golang-report/report.jsonl
go tool cover -html=cover.out -o golang-report/index.html

我们可以自己设置红线信息然后在后台看见测试报告,如果不符合红线设置代码是无法进入下一个阶段的

接下来的事就是根据自己的需要了,是根据二进制部署还是打包进docker

一些思考

其实现在单元测试不管是PHP还是GO从语言本身和环境生态上都支持得很到位了,我们进行单元测试有几个好处

  • 有效的避免功能迭代或变更时的BUG
  • 控制代码质量(自己都没法写出单测的代码是有问题的
  • 项目不断重构是个过程,单测可以在重构中起到比较好的作用
  • 考虑到更多的边界情况

实践

更新 2022年10月06日01:36:31

看了下这篇日志大概写了1个月了,这是一个月来我对go单元测试的完整流程实践。

GitHub Actions + codecov
https://github.com/zhangjunjie6b/mysql2elasticsearch

产考

http://c.biancheng.net/view/124.html

《大话设计模式》

图像 5

感受

我读这本书的痛点是从业6年写出的代码依旧很烂拿不出手。这本书读了1个月,反反复复读努力想去记住。但是发现没用,它是一本很好的书通过情景带入的方式详细的介绍了23种设计模式 套路是(抛出场景->介绍设计模式->用设计模式解决场景)最后用生动的选美方式做了全书总结,作者很用心是我没用。我的解决方法是买了3本关于代码质量的书(王争的《设计模式之美》,熊节译的《重构》)增加记忆摩擦,听不同大佬对设计模式的理解来让自己的代码变得漂亮。

请我喝杯咖啡吧~