Golang TDD

Preface 本文整理golang编码的单元测试常用示例,以及TDD的简要流程。 单元测试基础 单元测试文件以_test.go结尾,需要记住以下原则: 文件名必须是_test.go结尾的,这样在执行go test的时候才会执行到相应的代码 你必须import testing这个包 所有的测试用例函数必须是Test开头 测试用例会按照源代码中写的顺序依次执行 测试函数TestXxx()的参数是testing.T,我们可以使用该类型来记录错误或者是测试状态 测试格式:func TestXxx (t *testing.T),Xxx部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如Testintdiv是错误的函数名。 函数中通过调用testing.T的Error, Errorf, FailNow, Fatal, FatalIf方法,说明测试不通过,调用Log方法用来记录测试的信息。 Table-Driven-Testing 测试讲究 case 覆盖,当我们要覆盖更多 case 时,显然通过修改代码的方式很笨拙。这时我们可以采用 Table-Driven 的方式写测试,标准库中有很多测试是使用这种方式写的。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func TestFib(t *testing.T) { var fibTests = []struct { in int // input expected int // expected result }{ {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}, {7, 13}, } for _, tt := range fibTests { actual := Fib(tt.in) if actual != tt.expected { t.Errorf("Fib(%d) = %d; expected %d", tt.in, actual, tt.expected) } } } 由于我们使用的是 t.Errorf,即使其中某个 case 失败,也不会终止测试执行。 ...

December 19, 2020 · 17 min · 3480 words · Me

Golang并发调度

性能提升不会凭空出现,它总是伴随着代码复杂度的上升。 The performance improvement does not materialize from the air, it comes with code complexity increase. – Dmitry Vyukov Go 语言的调度器我认为应该是整个运行时最有趣的组件了。对于Go本身,它的设计和实现直接牵动了Go运行时的其他组件,也是和用户态代码直接打交道的部分;对于Go用户而言,调度器将其极为复杂的运行机制隐藏在了简单的关键字go下。为了保证高性能,调度器必须有效得利用计算的并行性和局部性原理;为了保证用户态的简洁,调度器必须高效得对调度用户态不可见的网络轮训器、垃圾回收器进行调度;为了保证代码执行的正确性,必须严格实现用户态代码的内存顺序等。总而言之,调度器的设计直接决定了Go运行时源码的表现形式。 设计原理 数据结构: MPG 调度器启动 创建Goroutine 调度循环 触发调度 线程管理 总结

December 17, 2020 · 1 min · 30 words · Me

Python类自定义

python类关键字 __init__ vs __new__ __init__为初始化方法,__new__为真正的构造函数。 描述符Descriptor __contains__ __slots__ 定制类 type() python作为动态语言,和静态语言最大的不同,即函数和类的定义,不是编译的时候创建的而是动态创建的。我们常见的对类的定义: 1 2 3 class Hello(object): def hello(self, name='world'): print('Hello, %s.' % name) >>> from hello import Hello >>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class 'hello.Hello'> type()函数可以查看一类类型或者变量的类型,Hello是一个class, 它的类型是个type,而h是一个instance, 它的类型就是class Hello。 同时有一个概念,就是type()不仅可以返回对象的类型,还可以创建出新的类型。我们可以不用定义class Hello() ...而动态创建出Hello类。 >>> def fn(self, name='world'): # 先定义函数 ... print('Hello, %s.' % name) ... >>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class >>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class '__main__.Hello'> 创建一个class对象,type()函数传入3个参数: ...

December 14, 2020 · 3 min · 490 words · Me

BDD: Ginkgo测试框架

Preface BDD和TDD都是test case first的实现,无非是把后者的test改成前者的behavior。在TDD中,关注的核心点是function,即认为程序最基本单元是function,其test case可以认为是unit test,TDD和unit test的区别是TDD强调测试和开发结合而成的工作流: 写test case -> 写代码 -> 通过测试,继续写更多测试,写一次循环。 而BDD比TDD更关注高层的行为,而不是函数级别的行为,也就是在BDD中,不会强调函数的功能正确,这是unit test应该做的事。BDD更关注user story,即用户在特定场景,与软件交互发生的行为,这个behavior指的就是高层模块的行为。 如何区分BDD和TDD,简单理解,TDD是给programmer的,用来验证开发者的最基本模块的功能:在什么输入,应该产生什么输出,保证实现的边界,健全性。而BDD,其test case描述的是更高级的模块行为,脱离了具体的实现,容易用自然语言去描述,也就是BDD是给product manager的,告诉其系统的行为。 BDD in golang ​ 实现的时候,我们需要把Given-When-Then这种story格式组织test case翻译为测试代码,通过一系列的assertion来检查实现是否符合test case的预期,我们完全可以直接通过golang自带的testing模块来实现,不过testing的功能有时候比较简陋,本文记录了用Ginkgo+Gomega来组织test case,让我们的测试语言更加接近自然语言。 二者结合的目的是,ginkgo实现了test case的组织,并加入了其他方便的功能: 初始化,后续处理,异步等等。而gomega设计的目的是与ginkgo一起工作,实现易读的assertion(ginkgo中称为match)功能。 Gomega is ginkgo's preferred matcher library 初始化 ginkgo依托golang原生testing框架,即可以用go test ./.. 执行,也可以通过ginkgo binrary安装go install github.com/onsi/ginkgo,封装了ginkgo测试框架的各种feature。 初始化首先进入待测试的package: cd /path/to/package 执行初始化: ginkgo bootstrap 生成以suite_test.go文件,接下来向suite添加测试specs,生成比如ginkgo_cart package测试文件。 ginkgo generate ginkgo_cart 运行 生成ginkgo_cart_test.go,注意测试文件在ginkgo_cart_testpackage, 需要import package ginkgo_cart,即BDD层级高于unit test, 不应该了解package内部的具体实现,测试package的外部接口即可。编写测试代码,运行go test ./..即可。 Ginkgo Keyword Ginkgo测试代码骨架由一系列keyword关联的闭包组成,常用的有: Describe/Context/When: 测试逻辑块 BeforeEach/AfterEach/JustBeforeEach/JustAfterEach: 初始化测试用例块 It: 单一Spec,测试case keyword的声明均为传入Body参数,比如Describe: ...

December 4, 2020 · 2 min · 395 words · Me

Golang内存管理

设计原则 现在我们来看 Go 中另一重要的关键组件:内存分配器。 Go 的内存分配器基于 Thread-Cache Malloc (tcmalloc) ,tcmalloc 为每个线程实现了一个本地缓存, 区分了小对象(小于 32kb)和大对象分配两种分配类型,其管理的内存单元称为 span。 我们不再介绍更多 tcmalloc 的具体细节,因为 Go 的内存分配器与 tcmalloc 存在一定差异。 这个差异来源于 Go 语言被设计为没有显式的内存分配与释放, 完全依靠编译器与运行时的配合来自动处理,因此也就造就了内存分配器、垃圾回收器两大组件。 我们知道,在计算机领域中,无外乎时间换空间、空间换时间。统一管理内存会提前分配或一次性释放一大块内存, 进而减少与操作系统沟通造成的开销,进而提高程序的运行性能。 支持内存管理另一个优势就是能够更好的支持垃圾回收,这一点我们留到垃圾回收器的章节中进行讨论。 主要结构 Go 的内存分配器主要包含以下几个核心组件: heapArena: 保留整个虚拟地址空间 mheap:分配的堆,在页大小为 8KB 的粒度上进行管理 mspan:是 mheap 上管理的一连串的页 mcentral:收集了给定大小等级的所有 span mcache:为 per-P 的缓存。 其中页是向操作系统申请内存的最小单位,目前设计为 8KB。 每一个结构虽然不都像是调度器 M/P/G 结构那样的大部头,但初次阅读这些结构时想要理清他们之间的关系还是比较麻烦的。 传统意义上的栈被 Go 的运行时霸占,不开放给用户态代码;而传统意义上的堆内存,又被 Go 运行时划分为了两个部分, 一个是 Go 运行时自身所需的堆内存,即堆外内存;另一部分则用于 Go 用户态代码所使用的堆内存,也叫做 Go 堆。 Go 堆负责了用户态对象的存放以及 goroutine 的执行栈。 Arena heapArena Go 堆被视为由多个 arena 组成,每个 arena 在 64 位机器上为 64MB,且起始地址与 arena 的大小对齐, 所有的 arena 覆盖了整个 Go 堆的地址空间。 ...

December 2, 2020 · 6 min · 1078 words · Me