Go_

  • content {:toc}

三、测试

测试是避免事物的最后一道屏障。

  • 回归测试
  • 集成测试
  • 单元测试

从上到下,覆盖率逐层变大,成本却逐层降低

Go 语言内置有对单元测试的支持,主要通过 testing 包实现。

  • 所有测试文件以_test.go结尾
  • func TestXxx(t *testing.T)
  • 初始化逻辑放到TestMain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func Add(a int, b int) int{
  return a+b
}

func TestAdd(t *testing.T) {
  output:=Add(1, 2)
  expectOutput:=3
  if output != expectOutput{
    t.Error("Add failed")
  }
}

assert

  • Go 语言内置的 testing 包提供了 assert 包来进行单元测试的断言,assert 能大大简化测试代码,不需要过多的 if 条件判断,只要最终结果不符合断言,就会自动标记为测试失败。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package Test

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

func Add(a int, b int) int {
   return a + b
}

func TestAdd(t *testing.T) {
   output := Add(1, 2)
   expectOutput := 3
   assert.Equal(t, expectOutput, output)
}

性能优化与软件质量

  • 软件质量至关重要
  • 在保证接口稳定的前提下改进具体实现
  • 测试用例: 覆盖尽可能多的场景,方便回归, 测试驱动开发
  • 文档:做了什么,没做什么,能达到怎样的效果
  • 隔离:通过选项控制是否开启优化
  • 可观测:必要的日志输出

自动内存管理主要管理的是动态内存动态内存指的是程序在运行时根据需求动态分配的内存,比如 C 语言中的 malloc() 函数分配的内存。

自动内存管理也称为垃圾回收,主要目的是由程序语言的运行时系统管理动态内存,这样做有以下两方面的好处:

  • 避免手动内存管理,专注于实现业务逻辑
  • 保证内存使用的正确性和安全性,比如 C 语言中的内存多次释放:double-free problem, 释放后再次使用 use-after-free problem

由此可见,手动释放内存存在很多问题,如果使用不当的话,可能会引起程序的崩溃、漏洞,而自动内存管理可以帮我们自动处理这些问题。

自动内存管理有 3 个主要的核心任务:

  1. 为新对象分配空间
  2. 找到存活对象
  3. 回收死亡对象的内存空间

基本概念

  • Mutator:业务线程,分配新对象,修改对象指向关系
  • Collector:GC 线程,找到存活对象,回收死亡对象的内存空间
  • Serial GC:只有一个 collector,会暂停 (STW)
  • Parallec GC:支持多个 collectors 同时回收 GC 算法,会暂停 (STW)
  • Concurrent GCmutator(s)collector(s) 可以同时执行,Collectors 必须感知对象指向关系的改变。

gc 算法

接下来将介绍两种常见的 GC 相关的技术:

  • 追踪垃圾回收 (Tracing garbage collection)
  • 引用计数 (Reference counting)

追踪垃圾分类

追踪垃圾回收,当一个对象的指针指向关系不可达的时候,该对象就要被回收了。

追踪垃圾回收算法垃圾回收步骤:

  1. 标记根对象
    • 标记包括 静态变量、全局变量、常量、线程栈等
  2. 标记:找到可达对象
    • 求指针指向关系的传递闭包:从根对象触发,找到所有可达对象
  3. 清理:所有不可达对象
    • 将存活对象复制到另外的内存空间 (Copying GC)
    • 将死亡对象的内存标记为” 可分配 “(Marking-sweep GC)
    • 移动并整理存活对象 (Mark-compact GC)

清理策略有很多种,在实际清理的时候应该根据对象的生命周期,使用不同的标记和清理策略。

分代 gc

分代GC(Generational GC) 是一种常见的内存管理方式,思想是基于分代假说 (Generational hypothesis)—— 大多数对象很快就死掉了 (most objects die young),很多对象在分配出来后很快就不再使用了。

每个对象都有年龄,也就是对象经历过 GC 的次数,比如经历了 2 次 GC 那么他的年龄就为 2。

分代GC 根据对象年龄的不同,把对象放在不同的区域,年轻代对象放在 Young Generation 区域,老年代放在 Old Generation 区域,这样做的目的为对年轻和老年的对象,制定不同的 GC策略,降低整体内存管理的开销。

年轻代为常规的对象分配,由于存活对象很少,可以采用 copying collection算法,GC 吞吐量很高。

老年代内对象趋向于一直活着,反复复制开销较大,可以采用 mark-sweep collection算法

引用计数

引用计数管理内存的方式为每个对象都有一个与之相关联的引用数目,对象存活的条件为当且仅当引用数大于 0。

引用计数管理内存的优点如下:

  • 内存管理的操作被平摊到程序执行过程中
  • 内存管理不需要了解 runtime 的实现细节,有一些库可以帮助实现引用计数,比如 C++ 智能指针 (smart pointer)。

当然引用计数也有缺点:

  • 维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
  • 无法回收环形数据结构 ——weak reference
  • 内存开销:每个对象都引入的额外内存空间存储引用数目
  • 回收内存时可能引发暂停 —— 大的数据结构
Licensed under CC BY-NC-SA 4.0
最后更新于 Oct 13, 2024 18:49 +0800
使用 Hugo 构建
主题 StackJimmy 设计