作为一名合格的开发者,不应该在程序开发完之后才开始写测试代码。
9.1 单元测试
单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。
测试的目的是确认目标代码在给定的场景下,有没有按照期望工作。
-
正向路径测试:正常执行的情况下,保证代码不产生错误的测试。
-
负向路径测试:保证代码不仅会产生错误,而且是预期错误的测试。
-
基础测试(basic test):只使用一组参数和结果来测试代码。
-
表组测试(table test):使用多组参数和结果来测试代码。
作为一名合格的开发者,不应该在程序开发完之后才开始写测试代码。
单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。
测试的目的是确认目标代码在给定的场景下,有没有按照期望工作。
正向路径测试:正常执行的情况下,保证代码不产生错误的测试。
负向路径测试:保证代码不仅会产生错误,而且是预期错误的测试。
基础测试(basic test):只使用一组参数和结果来测试代码。
表组测试(table test):使用多组参数和结果来测试代码。
Go 标准库是一组核心包,用来扩展和增强语言的能力。
这些包为语言增加了大量不同的类型。
开发人员可以直接使用这些类型,而不用再写自己的包或者去下载其他人发布的第三方包。
由于这些包和语言绑在一起发布,它们会得到以下特殊的保证:
本章学习 3 个可以在实际工程里使用的包,这 3 个包分别实现了不同的并发模式。
// Example is provided with help by Gabriel Aszalos.
// Package runner manages the running and lifetime of a process.
package runner
import (
"errors"
"os"
"os/signal"
"time"
)
// Runner runs a set of tasks within a given timeout and can be
// shut down on an operating system interrupt.
type Runner struct {
// interrupt channel reports a signal from the
// operating system.
interrupt chan os.Signal
// complete channel reports that processing is done.
complete chan error
// timeout reports that time has run out.
timeout <-chan time.Time
// tasks holds a set of functions that are executed
// synchronously in index order.
tasks []func(int)
}
// ErrTimeout is returned when a value is received on the timeout channel.
var ErrTimeout = errors.New("received timeout")
// ErrInterrupt is returned when an event from the OS is received.
var ErrInterrupt = errors.New("received interrupt")
// New returns a new ready-to-use Runner.
func New(d time.Duration) *Runner {
return &Runner{
interrupt: make(chan os.Signal, 1),
complete: make(chan error),
timeout: time.After(d),
}
}
// Add attaches tasks to the Runner. A task is a function that
// takes an int ID.
func (r *Runner) Add(tasks ...func(int)) {
r.tasks = append(r.tasks, tasks...)
}
// Start runs all tasks and monitors channel events.
func (r *Runner) Start() error {
// We want to receive all interrupt based signals.
signal.Notify(r.interrupt, os.Interrupt)
// Run the different tasks on a different goroutine.
go func() {
r.complete <- r.run()
}()
select {
// Signaled when processing is done.
case err := <-r.complete:
return err
// Signaled when we run out of time.
case <-r.timeout:
return ErrTimeout
}
}
// run executes each registered task.
func (r *Runner) run() error {
for id, task := range r.tasks {
// Check for an interrupt signal from the OS.
if r.gotInterrupt() {
return ErrInterrupt
}
// Execute the registered task.
task(id)
}
return nil
}
// gotInterrupt verifies if the interrupt signal has been issued.
func (r *Runner) gotInterrupt() bool {
select {
// Signaled when an interrupt event is sent.
case <-r.interrupt:
// Stop receiving any further signals.
signal.Stop(r.interrupt)
return true
// Continue running as normal.
default:
return false
}
}
Go 语言里的并发指的是能让某个函数独立于其它函数的能力。
Go 语言的并发同步模型来自一个叫做 通信顺序进程(Communicating Sequential Processes,CSP) 的泛型(paradigm)。
CSP 是一种消息传递模型,通过在 goroutine 之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问。
用于在 goroutine 之间同步和传递数据的关键数据类型叫做 通道(channel) 。
Go 语言是一种静态类型的编程语言。这就意味着,编译器需要在编译时知晓程序里每个值的类型。
值的类型给编译器提供两个信息:
Go 语言允许用户定义类型。当用户声明一个类型时,这个声明就给编译器提供了一个框架,告知必要的内存大小和表示信息。
使用 struct
关键字声明结构:
type user struct {
name string
email string
ext int
privileged bool
}
在 Go 语言里,数组是一个长度固定的数据类型,用户存储一段具有相同类型的元素的连续块。
数组占用的内存是连续分配的。
声明数组时需要指定内部存储的数据的类型,以及需要存储的元素的数量。
一旦声明,数组里存储的数据类型和数组长度就都不能改变了。
var array [5]int
在 Go 语言中,包是个非常重要的概念。
其设计理念是使用包来封装不同语义单元的功能。
所有的 .go 文件,除了空行和注释,都应该在第一行声明自己所属的包。
每个包都在单独的目录里。
同一个目录下的所有 .go 文件必须声明同一个包名。
给包命名的惯例是使用包所在目录的名字。
给包及其目录命名时,应该使用简洁、清晰且全小写的名字。
在 Go 语言中,命名为 main 的包具有特殊的含义。
所有用 Go 语言编译的可执行程序都必须有一个名叫 main 的包。
main()
函数是程序的入口。
程序编译时,会使用声明为 main 包的代码所在的目录的目录名作为二进制可执行文件的文件名。
示例代码:
git clone https://github.com/goinaction/code.git
package main
import (
"log"
"os"
_ "./matchers"
"./search"s
)
// init is called prior to main.
func init() {
// Change the device for logging to stdout.
log.SetOutput(os.Stdout)
}
// main is the entry point for the program.
func main() {
// Perform the search for the specified term.
search.Run("president")
}
编译 Go 程序时,编译器只会关注那些直接被引用的库。
JiaJia:
这是不是意味着 Go 不支持反射? Go 语言有提供反射功能的 reflect 包。 => Go 语言如何保证反射的包会被编译?
Go 语言对并发的支持是这门语言最重要的特性之一。 goroutine 很像线程,但是它占用的内存远少于线程,使用它需要的代码更少。
goroutine 是可以与其它 goroutine 并行执行的函数,同时也会与主程序(程序的入口)并行执行。