gomock简明教程

1.定义 gomock是一个用于Go语言的Mock框架, 它可以帮助开发者在单元测试中模拟(Mock)接口的行为, 从而隔离被测试代码与外部依赖,使测试更加独立、稳定和高效。 项目原地址为 golang/mock 2023年6月之后,代码库已经归档,官方推荐使用go.uber.org/mock 2.安装 go install go.uber.org/mock/mockgen@latest 3.生成Mock代码 有文件itf.go mockgen -source=./itf.go -destination=./mock.go -package=mymock -source:指定包含接口定义的源文件。 -destination:用于写入结果源代码的文件。 -package:指定生成的Mock代码所在的包名。 4.单元测试 预设的请求,可以按照顺序调用或非顺序调用 4.1 非顺序调用 import ( "fmt" "go.uber.org/mock/gomock" "testing" ) func TestFoo(t *testing.T) { // 创建Mock控制器 ctrl := gomock.NewController(t) defer ctrl.Finish() m := NewMockFoo(ctrl) m.EXPECT().Bar(1).Return(1) m.EXPECT().Bar(2).Return(2) fmt.Println("--1--", m.Bar(2)) fmt.Println("--2--", m.Bar(1)) } output: --1-- 2 --2-- 1 可以按照任意顺序调用Bar() 4.2 顺序调用 func TestFoo2(t *testing.T) { // 创建Mock控制器 ctrl := gomock.NewController(t) defer ctrl.Finish() m := NewMockFoo(ctrl) gomock.InOrder( // 必须按照指定顺序调用Bar() m.EXPECT().Bar(1).Return(1), m.EXPECT().Bar(2).Return(2), ) fmt.Println("--1--", m.Bar(2)) fmt.Println("--2--", m.Bar(1)) } 单元测试运行错误 ...

May 29, 2025 · 3 min

开源密码管理小工具 vearne/mypwbox

1. 引子 日常总有很多的密码需要存储,除了使用chrome浏览器的密码管理工具,还有一些其它场景,比如二次密码验证。 2019年萌叔开发了一款纯命令行管理工具 vearne/passwordbox 2024年底,我又在AI的帮助下开发了跨平台的App。vearne/mypwbox 2. 特征和用法 Flutter编写,支持跨平台(需要自己build) 支持国际化,当前支持 中文 或 English 支持使用对象存储(S3协议)进行在线同步&备份(可选,默认为离线) 支持一次性密码 任何支持S3协议的对象存储都可以被使用 AWS S3 Aliyun OSS Tencent COS MinIO 如果密码被用于二次验证且形如 otpauth://totp/ut:vearne?algorithm=SHA1&digits=6&issuer=ut&period=30&secret=XXXXXXXXXXXXXXXXXXXXXXXX 则可以显示对应的基于时间的一次性密码 警告: 关键密码不宜存储于电子文档中,最安全的方式是牢记于心。 作者: vearne 文章标题: 开源密码管理小工具 vearne/mypwbox 发表时间: 2025年01月17日 文章链接: https://vearne.cc/archives/40249 版权说明: CC BY-NC-ND 4.0 DEED

January 17, 2025 · 1 min

Content-Type请求示例

Content-Type请求示例 1.application/json 请求 python代码示例 import requests import json data = { 'a': 123, 'b': 456 } ## headers中添加上content-type这个参数,指定为json格式 headers = {'Content-Type': 'application/json'} ## post的时候,将data字典形式的参数用json包转换成json格式。 response = requests.post(url='http://127.0.0.1:9000/api/ttt', headers=headers, data=json.dumps(data)) 实际发出的请求体 POST /api/ttt HTTP/1.1 Host: 127.0.0.1:9000 User-Agent: python-requests/2.31.0 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive Content-Type: application/json Content-Length: 20 {"a": 123, "b": 456} 2. application/x-www-form-urlencoded 请求 python代码示例 import requests data = { 'a': 123, 'b': 456 } ## headers中添加上content-type这个参数,指定为x-www-form-urlencoded格式 headers = {'Content-Type': 'application/x-www-form-urlencoded'} ## post的时候,默认按x-www-form-urlencoded格式处理 response = requests.post(url='http://127.0.0.1:9000/api/ttt', headers=headers, data=data) 实际发出的请求体 POST /api/ttt HTTP/1.1 Host: 127.0.0.1:9000 User-Agent: python-requests/2.31.0 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive Content-Type: application/x-www-form-urlencoded Content-Length: 11 a=123&b=456

November 12, 2024 · 1 min

玩转高性能日志库ZAP (7)-标识

1.前言 在Java中我们常常会这样使用日志 import org.slf4j.Logger; import org.slf4j.LoggerFactory; class Main { final static Logger log = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { log.info("info, hello {}", "slf4j"); log.error("error"); } } 输出 6:30:03.350 [main] INFO Main - info, hello slf4j 16:30:03.356 [main] ERROR Main - error 这里在每条日志中都有类名,可以很容易从文件中过滤出这个类相关的日志。 2.zap中使用name标识 zap中的玩法 package main import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "time" ) func main() { config := zap.NewProductionConfig() config.Encoding = "console" config.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") logger, _ := config.Build() defer logger.Sync() // 输出该条日志在代码文件中的行号 logger = logger.WithOptions(zap.AddCaller()) logger.Info("failed to fetch URL", zap.Int("attempt", 3), zap.Duration("backoff", time.Second), ) go func() { // worker1 // 携带name标识 logger1 := logger.Named("worker1") for { time.Sleep(time.Millisecond * 200) logger1.Info("hello1") } }() go func() { // worker2 logger2 := logger.Named("worker2") for { time.Sleep(time.Millisecond * 200) logger2.Info("hello2") } }() time.Sleep(3 * time.Second) } 输出 ...

October 9, 2024 · 2 min

channel的有趣用法

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引子 萌叔在阅读tailsamplingprocessor源码时,发现channel的一种有趣的玩法,这里记录一下 FIFO 队列 tailsamplingprocessor中通过NumTraces 设置内存中最多保存的trace的数量, 超过这个阈值,就从按照先进先出的原则,删除最先进入队列的trace // NumTraces is the number of traces kept on memory. Typically most of the data // of a trace is released after a sampling decision is taken. NumTraces uint64 `mapstructure:"num_traces"` processor.go postDeletion := false currTime := time.Now() for !postDeletion { select { case tsp.deleteChan <- id: postDeletion = true default: traceKeyToDrop := <-tsp.deleteChan tsp.dropTrace(traceKeyToDrop, currTime) } } channel刚好满足这个特性, 如果tsp.deleteChan 没有满, 则往tsp.deleteChan写入一个traceID; 如果tsp.deleteChan已经装满,则从队列(tsp.deleteChan)中取出头部元素, 其实就是最先进入队列的traceID,将其对应的trace信息从内存中删除(tsp.dropTrace())。 ...

April 17, 2024 · 1 min

玩转GRPC(2)-状态码

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 和Http协议一样,gRPC协议也提供了一组基础的状态码。 一般情况下,仅使用这组基础状态码就可以反应gPRC请求处理的所有错误信息。 2. 示例代码 grpc_status_error server var counter uint64 = 0 const ( port = ":50051" ) type server struct { pb.UnimplementedGreeterServer } // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { fmt.Println("pb.HelloRequest", in.Name) md, ok := metadata.FromIncomingContext(ctx) if !ok { fmt.Printf("get metadata error") } for key, val := range md { fmt.Printf("%v:%v\n", key, val) } x := atomic.AddUint64(&counter, 1) % 3 switch x { case 0: return &pb.HelloReply{Message: "Hello " + in.Name}, nil case 1: return nil, status.Error(codes.DataLoss, "--DataLoss--") default: return nil, status.Error(codes.Unauthenticated, "--Unauthenticated--") } } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) // Register reflection service on gRPC server. reflection.Register(s) log.Println("say_hello_grpc starting...") if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } client func main() { conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() client := pb.NewGreeterClient(conn) // Contact the server and print out its response. ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := client.SayHello(ctx, &pb.HelloRequest{Name: "lily"}) if err != nil { s := status.Convert(err) log.Printf("code:%v, message:%v\n", s.Code(), s.Message()) } else { log.Printf("reply:%v\n", r.String()) } } 也可以使用status.Code(err),直接得到code 当 err == nil 时,code为codes.OK, 值为0 3. wireshark 抓包 wireshark 抓包gRPC参考 Analyzing gRPC messages using Wireshark ...

February 5, 2024 · 2 min

skywalking-go 原理剖析

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 本文代码示例基于 skywalking-go v0.3.0 1. 引言 apache/skywalking-go 的内部实现比较复杂,本文针对几个核心的点进行展开,以便于大家更好的阅读和理解。 2.TracingID生成 详细参考代码 id.go skywalking-go中的TraceID形如: “ab95670cae9d11ee87ca5626e1cdcfe2.14.39165238789840005” 一共由4部分组成: 1)uuid 使用uuid version1 2)goroutine ID,golang协程ID 3)timestamp 自1900年以来的毫秒值 4)sequence, 从0 ~ 10000 3. 跨协程传递Tracing信息 在没有skywalking-go的情况下,如果想在多个协程中传递Tracing信息, 开发者必须要用到context。使用skywalking-go之后,很多细节,开发者无需关心。 下面是一个在gin中使用多协程的示例,完整代码见参考资料2 r.GET("/ping", func(c *gin.Context) { g, _ := errgroup.WithContext(context.Background()) g.Go(func() error { val, err := rdb.Incr(context.Background(), "helloCounter2").Result() zlog.Info("ping", zap.Int64("val", val), zap.Error(err)) return nil }) g.Go(func() error { hsetRes, err := rdb.HSet(context.Background(), "xyz", "def", 0).Result() zlog.Info("ping", zap.Int64("setRes", hsetRes), zap.Error(err)) return nil }) c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) ...

January 10, 2024 · 2 min

C++导致悬垂指针的一个场景

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 警告: 此文章对读者没有价值 遇到的问题 #include <iostream> #include <vector> using namespace std; class Car { public: int age; Car(int a) : age(a) { cout << "Car():" << age << endl; } ~Car() { cout << "~Car():" << age << endl; } }; int main() { vector<Car *> v; for (int i = 0; i < 10; i++) { Car tmp(i); v.push_back(&tmp); } for (auto c : v) { cout << c->age << endl; } } 执行输出: ...

August 17, 2023 · 1 min

从MySQL client使用引出的bug聊起

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引子 前几天一个同事在MySQL实例上执行一些数据处理,程序大致如下: modify1.go package main import ( "context" "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "log" "time" ) func main() { db, err := sql.Open("mysql", "testdb_user:12345678@tcp(192.168.2.100:25037)/testdb?charset=utf8&loc=Asia%2FShanghai&parseTime=true") if err != nil { fmt.Println("===0===", err) } defer db.Close() tx, err := db.Begin() if err != nil { fmt.Println("===0===", err) } // 第1个SQL rows, err := tx.QueryContext(context.Background(), "select mtime, money from test where money > ?", 1) if err != nil { log.Fatal(err) } defer rows.Close() // 迭代查询结果 for rows.Next() { var mtime time.Time var money int if err := rows.Scan(&mtime, &money); err != nil { // Check for a scan error. // Query rows will be closed with defer. log.Fatal(err) } log.Println(mtime, money) // 第2个SQL result, err := tx.ExecContext(context.Background(), "insert into test2(`mtime`, `money`) values (?, ?)", mtime, money) if err != nil { log.Fatal(err) } rowsAffected, _ := result.RowsAffected() lastInsertIdresult, _ := result.LastInsertId() log.Println(rowsAffected, lastInsertIdresult) } tx.Commit() } 报错信息如下: ...

June 18, 2023 · 3 min

Golang 模糊测试 简明教程

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 Golang1.18 引入了一个模糊测试的功能。文本萌叔将会简单介绍下这个特性。 2.模糊测试 传送门 比如我们编写了一个工具包来做除法 package fuzz func Div(a, b int) int { return a / b } 由于疏忽忽略了某些边界条件。显然在上面的代码中,b不能等于0,否则会发生运行时错误。 普通的单元测试 func TestDiv(t *testing.T) { testcases := []struct { a, b, want int }{ {10, 2, 5}, {5, 3, 1}, {-6, 3, -2}, {-6, -3, 2}, } for _, tc := range testcases { result := Div(tc.a, tc.b) if Div(tc.a, tc.b) != tc.want { t.Errorf(&quot;Div: %q, want %q&quot;, result, tc.want) } } } 由于对分支条件和异常情况考虑的不周全,自测的单元测试能够正常通过。但是一旦b=0,程序将会崩溃。 ...

September 14, 2022 · 1 min