├── LICENSE ├── README.md ├── monitor.go ├── utest.go └── utest_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 3 | Version 2, December 2004 4 | 5 | Copyright (C) 2004 Sam Hocevar 6 | 7 | Everyone is permitted to copy and distribute verbatim or modified 8 | copies of this license document, and changing it is allowed as long 9 | as the name is changed. 10 | 11 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 12 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 13 | 14 | 0. You just DO WHAT THE FUCK YOU WANT TO. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 介绍 2 | ==== 3 | 4 | 这是一个单元测试工具库,用来降低Go语言项目单元测试的代码复杂度。 5 | 6 | 工具接口 7 | ======= 8 | 9 | 以下先做一个简单对比,使用utest库之前的单元测试如下: 10 | 11 | ```go 12 | func VerifyBuffer(t *testing.T, buffer InBuffer) { 13 | if buffer.ReadUint8() != 1 { 14 | t.Fatal("buffer.ReadUint8() != 1") 15 | } 16 | 17 | if buffer.ReadByte() != 99 { 18 | t.Fatal("buffer.ReadByte() != 99") 19 | } 20 | 21 | if buffer.ReadInt8() != -2 { 22 | t.Fatal("buffer.ReadInt8() != -2") 23 | } 24 | 25 | if buffer.ReadUint16() != 0xFFEE { 26 | t.Fatal("buffer.ReadUint16() != 0xFFEE") 27 | } 28 | 29 | if buffer.ReadInt16() != 0x7FEE { 30 | t.Fatal("buffer.ReadInt16() != 0x7FEE") 31 | } 32 | 33 | if buffer.ReadUint32() != 0xFFEEDDCC { 34 | t.Fatal("buffer.ReadUint32() != 0xFFEEDDCC") 35 | } 36 | 37 | if buffer.ReadInt32() != 0x7FEEDDCC { 38 | t.Fatal("buffer.ReadInt32() != 0x7FEEDDCC") 39 | } 40 | 41 | if buffer.ReadUint64() != 0xFFEEDDCCBBAA9988 { 42 | t.Fatal("buffer.ReadUint64() != 0xFFEEDDCCBBAA9988") 43 | } 44 | 45 | if buffer.ReadInt64() != 0x7FEEDDCCBBAA9988 { 46 | t.Fatal("buffer.ReadInt64() != 0x7FEEDDCCBBAA9988") 47 | } 48 | 49 | if buffer.ReadRune() != '好' { 50 | t.Fatal(`buffer.ReadRune() != '好'`) 51 | } 52 | 53 | if buffer.ReadString(6) != "Hello1" { 54 | t.Fatal(`buffer.ReadString() != "Hello"`) 55 | } 56 | 57 | if bytes.Equal(buffer.ReadBytes(6), []byte("Hello2")) != true { 58 | t.Fatal(`bytes.Equal(buffer.ReadBytes(5), []byte("Hello")) != true`) 59 | } 60 | 61 | if bytes.Equal(buffer.ReadSlice(6), []byte("Hello3")) != true { 62 | t.Fatal(`bytes.Equal(buffer.ReadSlice(5), []byte("Hello")) != true`) 63 | } 64 | } 65 | ``` 66 | 67 | 使用utest库重构后的单元测试如下: 68 | 69 | ```go 70 | func VerifyBuffer(t *testing.T, buffer InBuffer) { 71 | utest.Equal(t, buffer.ReadByte(), 99) 72 | utest.Equal(t, buffer.ReadInt8(), -2) 73 | utest.Equal(t, buffer.ReadUint8(), 1) 74 | utest.Equal(t, buffer.ReadInt16(), 0x7FEE) 75 | utest.Equal(t, buffer.ReadUint16(), 0xFFEE) 76 | utest.Equal(t, buffer.ReadInt32(), 0x7FEEDDCC) 77 | utest.Equal(t, buffer.ReadUint32(), 0xFFEEDDCC) 78 | utest.Equal(t, buffer.ReadInt64(), 0x7FEEDDCCBBAA9988) 79 | utest.Equal(t, buffer.ReadUint64(), 0xFFEEDDCCBBAA9988) 80 | utest.Equal(t, buffer.ReadRune(), '好') 81 | utest.Equal(t, buffer.ReadString(6), "Hello1") 82 | utest.Equal(t, buffer.ReadBytes(6), []byte("Hello2")) 83 | utest.Equal(t, buffer.ReadSlice(6), []byte("Hello3")) 84 | } 85 | ``` 86 | 87 | 在不牺牲单元测试结果输出的清晰性的前提下,utest可以减少很多不必要的判断语句和错误信息。 88 | 89 | 同时utest还会在测试失败是输出必要数据方便调试,例如: 90 | 91 | ``` 92 | $ go test -v 93 | === RUN Test_All 94 | utest.go:35: check fail 95 | args[0] = 1 96 | args[1] = 2 97 | args[2] = 3 98 | utest_test.go:10: not nil 99 | v = "io: read/write on closed pipe" 100 | utest_test.go:11: not equal 101 | a = 1 102 | b = 2 103 | utest_test.go:12: not equal 104 | a = 1.233 105 | b = 3.333 106 | utest_test.go:13: not equal 107 | a = '你' = 20320 108 | b = '好' = 22909 109 | utest_test.go:14: not equal 110 | a = "sadkfjsl" 111 | b = "sdfs*\r\n" 112 | utest_test.go:15: not equal 113 | a = []byte{0x1, 0x2, 0x3, 0x3} 114 | b = []byte{0x3, 0x4, 0x5, 0x6} 115 | utest_test.go:17: not equal 116 | a = []int{1, 2, 3} 117 | b = []int{3, 4, 5} 118 | utest_test.go:18: not deep equal 119 | a = []int{1, 2, 3} 120 | b = []int{3, 4, 5} 121 | --- FAIL: Test_All (0.00s) 122 | FAIL 123 | exit status 1 124 | FAIL vendor/github.com/funny/utest 0.006s 125 | ``` 126 | 127 | 如果单纯靠testing包做单元测试,就需要加判断和打印才能做到。 128 | 129 | utest支持以下几种测试检查: 130 | 131 | ```go 132 | // 自定义条件的断言,断言失败时测试立即终止 133 | // 支持变长参数的数据打印,测试失败时这些数据将被打印出来 134 | utest.Assert(t, condition, args...) 135 | 136 | // 同Assert,区别是Check失败时测试不会立即终止执行 137 | utest.Check(t, condition, args...) 138 | 139 | // 断言v必须为nil,否则测试失败 140 | utest.IsNil(t, v) 141 | // 同IsNil,失败时测试立即终止 142 | utest.IsNilNow(t, v) 143 | 144 | // 断言v不能为nil,否则测试失败 145 | utest.NotNil(t, v) 146 | utest.NotNilNow(t, v) 147 | 148 | // 149 | // 断言a和b必须相等,此函数只支持以下数据类型: 150 | // int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, rune, byte, string, []byte 151 | // []int, []int8, []int16, []int32, []int64, []uint, []uint8, []uint16, []uint32, []uint64, []rune 152 | // 或者实现了utest.Equals接口的自定义类型。 153 | // 当为简单类型时,a和b必须是同类型,只有当b为int类型时,函数内部才会尝试做类型转换,这个设计是为了适应常量值的用法: 154 | // utest.Equal(t, GetUint64(), 123) 155 | // 156 | utest.Equal(t, a, b) 157 | utest.EqualNow(t, a, b) 158 | 159 | // 当Equal无法满足测试需要时可以使用次函数,但是请记得次函数开销较大 160 | utest.DeepEqual(t, a, b) 161 | utest.DeepEqualNow(t, a, b) 162 | ``` 163 | 164 | 进程监控 165 | ======= 166 | 167 | 此外,在进行一些复杂的多线程单元测试的时候,可能出现死锁的情况,或者进行benchmark的时候,需要知道过程中内存的增长情况和GC情况。 168 | 169 | utest为这些情况提供了一个统一的监控功能,在单元测试运行目录下使用以下方法可以获取到单元测试过程中的信息: 170 | 171 | ```shell 172 | echo 'lookup goroutine' > utest.cmd 173 | ``` 174 | 175 | 以上shell脚本将使utest自动输出goroutine堆栈跟踪信息到`utest.goroutine`文件。 176 | 177 | utest支持以下几种监控命令: 178 | 179 | ``` 180 | lookup goroutine - 获取当前所有goroutine的堆栈跟踪信息,输出到utest.goroutine文件,用于排查死锁等情况 181 | lookup heap - 获取当前内存状态信息,输出到utest.heap文件,包含内存分配情况和GC暂停时间等 182 | lookup threadcreate - 获取当前线程创建信息,输出到utest.thread文件,通常用来排查CGO的线程使用情况 183 | ``` 184 | 185 | 此外你还可以通过注册`utest.CommandHandler`回调来添加自己的监控命令支持。 186 | -------------------------------------------------------------------------------- /monitor.go: -------------------------------------------------------------------------------- 1 | package utest 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "runtime/pprof" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // Command handler 12 | var CommandHandler func(string) bool 13 | 14 | func init() { 15 | go func() { 16 | for { 17 | if input, err := ioutil.ReadFile("utest.cmd"); err == nil && len(input) > 0 { 18 | ioutil.WriteFile("utest.cmd", []byte(""), 0744) 19 | 20 | cmd := strings.Trim(string(input), " \n\r\t") 21 | 22 | var ( 23 | profile *pprof.Profile 24 | filename string 25 | ) 26 | 27 | switch cmd { 28 | case "lookup goroutine": 29 | profile = pprof.Lookup("goroutine") 30 | filename = "utest.goroutine" 31 | case "lookup heap": 32 | profile = pprof.Lookup("heap") 33 | filename = "utest.heap" 34 | case "lookup threadcreate": 35 | profile = pprof.Lookup("threadcreate") 36 | filename = "utest.thread" 37 | default: 38 | if CommandHandler == nil || !CommandHandler(cmd) { 39 | println("unknow command: '" + cmd + "'") 40 | } 41 | } 42 | 43 | if profile != nil { 44 | file, err := os.Create(filename) 45 | if err != nil { 46 | println("couldn't create " + filename) 47 | } else { 48 | profile.WriteTo(file, 2) 49 | } 50 | } 51 | } 52 | time.Sleep(2 * time.Second) 53 | } 54 | }() 55 | } 56 | -------------------------------------------------------------------------------- /utest.go: -------------------------------------------------------------------------------- 1 | package utest 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math" 7 | "os" 8 | "reflect" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "testing" 13 | "unicode" 14 | "unsafe" 15 | ) 16 | 17 | var ( 18 | sizeOfInt = int(unsafe.Sizeof(int(0))) 19 | sizeOfInt16 = int(unsafe.Sizeof(int16(0))) 20 | sizeOfInt32 = int(unsafe.Sizeof(int32(0))) 21 | sizeOfInt64 = int(unsafe.Sizeof(int64(0))) 22 | sizeOfUint = int(unsafe.Sizeof(uint(0))) 23 | sizeOfUint16 = int(unsafe.Sizeof(uint16(0))) 24 | sizeOfUint32 = int(unsafe.Sizeof(uint32(0))) 25 | sizeOfUint64 = int(unsafe.Sizeof(uint64(0))) 26 | sizeOfFloat32 = int(unsafe.Sizeof(float32(0))) 27 | sizeOfFloat64 = int(unsafe.Sizeof(float64(0))) 28 | ) 29 | 30 | type toString interface { 31 | String() string 32 | } 33 | 34 | type Equals interface { 35 | Equals(interface{}) bool 36 | } 37 | 38 | func Check(t *testing.T, condition bool, args ...interface{}) bool { 39 | return check(t, condition, "check", t.Fail, args...) 40 | } 41 | 42 | func Assert(t *testing.T, condition bool, args ...interface{}) { 43 | check(t, condition, "assert", t.FailNow, args...) 44 | } 45 | 46 | func check(t *testing.T, condition bool, tp string, f func(), args ...interface{}) bool { 47 | if condition { 48 | return true 49 | } 50 | var buf bytes.Buffer 51 | buf.WriteString(tp + " fail\n") 52 | for i := 0; i < len(args); i++ { 53 | buf.WriteString(" args[") 54 | buf.WriteString(strconv.Itoa(i)) 55 | buf.WriteString("] = %#v") 56 | if i < len(args)-1 { 57 | buf.WriteByte('\n') 58 | } 59 | } 60 | log(2, fmt.Sprintf(buf.String(), args...)) 61 | f() 62 | return false 63 | } 64 | 65 | func IsNil(t *testing.T, v interface{}) bool { 66 | return isNil(t, v, t.Fail) 67 | } 68 | 69 | func IsNilNow(t *testing.T, v interface{}) { 70 | isNil(t, v, t.FailNow) 71 | } 72 | 73 | func isNil(t *testing.T, v interface{}, f func()) bool { 74 | if v == nil { 75 | return true 76 | } 77 | switch vv := v.(type) { 78 | case toString: 79 | log(2, fmt.Sprintf(`not nil 80 | v = %#v`, vv.String())) 81 | case error: 82 | log(2, fmt.Sprintf(`not nil 83 | v = %#v`, vv.Error())) 84 | case []byte: 85 | log(2, fmt.Sprintf(`not nil 86 | v = %#v`, vv)) 87 | default: 88 | log(2, fmt.Sprintf(`not nil 89 | v = %v`, vv)) 90 | } 91 | f() 92 | return false 93 | } 94 | 95 | func NotNil(t *testing.T, v interface{}) bool { 96 | return notNil(t, v, t.Fail) 97 | } 98 | 99 | func NotNilNow(t *testing.T, v interface{}) { 100 | notNil(t, v, t.FailNow) 101 | } 102 | 103 | func notNil(t *testing.T, v interface{}, f func()) bool { 104 | if v != nil { 105 | return true 106 | } 107 | log(2, fmt.Sprintf(`is nil`)) 108 | f() 109 | return false 110 | } 111 | 112 | func DeepEqual(t *testing.T, a, b interface{}) bool { 113 | return deepEqual(t, a, b, t.Fail) 114 | } 115 | 116 | func DeepEqualNow(t *testing.T, a, b interface{}) { 117 | deepEqual(t, a, b, t.FailNow) 118 | } 119 | 120 | func deepEqual(t *testing.T, a, b interface{}, f func()) bool { 121 | if reflect.DeepEqual(a, b) { 122 | return true 123 | } 124 | log(2, fmt.Sprintf(`not deep equal 125 | a = %#v 126 | b = %#v`, a, b)) 127 | f() 128 | return false 129 | } 130 | 131 | func Equal(t *testing.T, a, b interface{}) bool { 132 | return equal(t, a, b, t.Fail) 133 | } 134 | 135 | func EqualNow(t *testing.T, a, b interface{}) { 136 | equal(t, a, b, t.FailNow) 137 | } 138 | 139 | func equal(t *testing.T, a, b interface{}, f func()) bool { 140 | if a == nil || b == nil { 141 | return a == b 142 | } 143 | var ok bool 144 | var printable bool 145 | switch va := a.(type) { 146 | case int: 147 | ok = va == b.(int) 148 | case int8: 149 | ok = va == int8Val(b) 150 | case int16: 151 | ok = va == int16Val(b) 152 | //case int32: 153 | //ok = va == b.(int32) 154 | case rune: 155 | vb := int32Val(b) 156 | ok = va == vb 157 | printable = unicode.IsPrint(va) && unicode.IsPrint(rune(vb)) 158 | case int64: 159 | ok = va == int64Val(b) 160 | case uint: 161 | ok = va == uintVal(b) 162 | case uint8: 163 | ok = va == uint8Val(b) 164 | case uint16: 165 | ok = va == uint16Val(b) 166 | case uint32: 167 | ok = va == uint32Val(b) 168 | case uint64: 169 | ok = va == uint64Val(b) 170 | case float32: 171 | ok = va == float32Val(b) 172 | case float64: 173 | ok = va == float64Val(b) 174 | case string: 175 | ok = va == b.(string) 176 | case []byte: 177 | ok = bytes.Equal(va, b.([]byte)) 178 | case []int: 179 | ok = unsafeEqual(a, b.([]int), sizeOfInt) 180 | case []int16: 181 | ok = unsafeEqual(a, b.([]int16), sizeOfInt16) 182 | case []int32: 183 | ok = unsafeEqual(a, b.([]int32), sizeOfInt32) 184 | case []int64: 185 | ok = unsafeEqual(a, b.([]int64), sizeOfInt64) 186 | case []uint: 187 | ok = unsafeEqual(a, b.([]uint), sizeOfUint) 188 | case []uint16: 189 | ok = unsafeEqual(a, b.([]uint16), sizeOfUint16) 190 | case []uint32: 191 | ok = unsafeEqual(a, b.([]uint32), sizeOfUint32) 192 | case []uint64: 193 | ok = unsafeEqual(a, b.([]uint64), sizeOfUint64) 194 | case []float32: 195 | ok = unsafeEqual(a, b.([]float32), sizeOfFloat32) 196 | case []float64: 197 | ok = unsafeEqual(a, b.([]float64), sizeOfFloat64) 198 | case Equals: 199 | ok = va.Equals(b) 200 | default: 201 | ok = reflect.DeepEqual(a, b) 202 | } 203 | if ok { 204 | return true 205 | } 206 | if printable { 207 | log(2, fmt.Sprintf(`not equal 208 | a = '%c' = %#v 209 | b = '%c' = %#v`, a, a, b, b)) 210 | } else { 211 | log(2, fmt.Sprintf(`not equal 212 | a = %#v 213 | b = %#v`, a, b)) 214 | } 215 | f() 216 | return false 217 | } 218 | 219 | func int8Val(b interface{}) int8 { 220 | switch v := b.(type) { 221 | case int: 222 | if int(math.MinInt8) <= v && v <= int(math.MaxInt8) { 223 | return int8(v) 224 | } 225 | case int8: 226 | return int8(v) 227 | } 228 | panic("can't convert to int8 value") 229 | } 230 | 231 | func uint8Val(b interface{}) uint8 { 232 | switch v := b.(type) { 233 | case int: 234 | if 0 <= v && v <= int(math.MaxUint8) { 235 | return uint8(v) 236 | } 237 | case uint8: 238 | return uint8(v) 239 | } 240 | panic("can't convert to uint8 value") 241 | } 242 | 243 | func int16Val(b interface{}) int16 { 244 | switch v := b.(type) { 245 | case int: 246 | if int(math.MinInt16) <= v && v <= int(math.MaxInt16) { 247 | return int16(v) 248 | } 249 | case int16: 250 | return int16(v) 251 | } 252 | panic("can't convert to int16 value") 253 | } 254 | 255 | func uint16Val(b interface{}) uint16 { 256 | switch v := b.(type) { 257 | case int: 258 | if 0 <= v && v <= int(math.MaxUint16) { 259 | return uint16(v) 260 | } 261 | case uint16: 262 | return uint16(v) 263 | } 264 | panic("can't convert to uint16 value") 265 | } 266 | 267 | func int32Val(b interface{}) int32 { 268 | switch v := b.(type) { 269 | case int: 270 | if int(math.MinInt32) <= v && v <= int(math.MaxInt32) { 271 | return int32(v) 272 | } 273 | case int32: 274 | return int32(v) 275 | } 276 | panic("can't convert to int32 value") 277 | } 278 | 279 | func uint32Val(b interface{}) uint32 { 280 | switch v := b.(type) { 281 | case int: 282 | if 0 <= v && v <= int(math.MaxUint32) { 283 | return uint32(v) 284 | } 285 | case uint32: 286 | return uint32(v) 287 | } 288 | panic("can't convert to uint32 value") 289 | } 290 | 291 | func int64Val(b interface{}) int64 { 292 | switch v := b.(type) { 293 | case int: 294 | return int64(v) 295 | case int64: 296 | return int64(v) 297 | } 298 | panic("can't convert to int64 value") 299 | } 300 | 301 | func uintVal(b interface{}) uint { 302 | switch v := b.(type) { 303 | case int: 304 | if 0 <= v { 305 | return uint(v) 306 | } 307 | case uint: 308 | return uint(v) 309 | } 310 | panic("can't convert to uint value") 311 | } 312 | 313 | func uint64Val(b interface{}) uint64 { 314 | switch v := b.(type) { 315 | case int: 316 | if 0 <= v { 317 | return uint64(v) 318 | } 319 | case uint64: 320 | return uint64(v) 321 | } 322 | panic("can't convert to uint64 value") 323 | } 324 | 325 | func float32Val(b interface{}) float32 { 326 | switch v := b.(type) { 327 | case int: 328 | return float32(v) 329 | case float32: 330 | return float32(v) 331 | } 332 | panic("can't convert to float32 value") 333 | } 334 | 335 | func float64Val(b interface{}) float64 { 336 | switch v := b.(type) { 337 | case int: 338 | return float64(v) 339 | case float32: 340 | return float64(v) 341 | case float64: 342 | return float64(v) 343 | } 344 | panic("can't convert to float64 value") 345 | } 346 | 347 | func unsafeEqual(ia, ib interface{}, size int) bool { 348 | a := (*reflect.SliceHeader)((*(*[2]unsafe.Pointer)(unsafe.Pointer(&ia)))[1]) 349 | b := (*reflect.SliceHeader)((*(*[2]unsafe.Pointer)(unsafe.Pointer(&ib)))[1]) 350 | return bytes.Equal( 351 | *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 352 | Data: a.Data, 353 | Cap: a.Cap * size, 354 | Len: a.Len * size, 355 | })), 356 | *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 357 | Data: b.Data, 358 | Cap: b.Cap * size, 359 | Len: b.Len * size, 360 | })), 361 | ) 362 | } 363 | 364 | func log(depth int, val string) { 365 | if _, file, line, ok := runtime.Caller(1 + depth); ok { 366 | // Truncate file name at last file name separator. 367 | if index := strings.LastIndex(file, "/"); index >= 0 { 368 | file = file[index+1:] 369 | } else if index = strings.LastIndex(file, "\\"); index >= 0 { 370 | file = file[index+1:] 371 | } 372 | fmt.Fprintf(os.Stderr, " %s:%d: %s\n", file, line, val) 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /utest_test.go: -------------------------------------------------------------------------------- 1 | package utest 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | ) 7 | 8 | func Test_All(t *testing.T) { 9 | Check(t, 1 > 2, 1, 2, 3) 10 | IsNil(t, io.ErrClosedPipe) 11 | Equal(t, 1, 2) 12 | Equal(t, 1.233, 3.333) 13 | Equal(t, '你', '好') 14 | Equal(t, "sadkfjsl", "sdfs*\r\n") 15 | Equal(t, []byte{1, 2, 3, 3}, []byte{3, 4, 5, 6}) 16 | Equal(t, []int{1, 2, 3}, []int{1, 2, 3}) 17 | Equal(t, []int{1, 2, 3}, []int{3, 4, 5}) 18 | DeepEqual(t, []int{1, 2, 3}, []int{3, 4, 5}) 19 | Equal(t, int32(100), 100) 20 | IsNilNow(t, 111) 21 | IsNilNow(t, 222) 22 | } 23 | --------------------------------------------------------------------------------