单测架构分析
小徐先生的单测经历 的学习笔记,视频真干货!
引言#
1 | func LearnGO(){ |
在我们写代码的过程中经常会迷茫怎么写单测,为什么单测难写
- 单测逻辑的不确定性
- 第三方依赖的复杂性导致单测结果的不稳定性
下面讲的所有内容都是针对第二点而言,看上面的代码,我们发现当我们使用http.post
的时候,我们是不知道我们所要访问的网站是怎么样的,会返回什么值,网站的状态好坏,这些都会影响到我们单测的值,而我们在单测的时候,肯定会希望对功能的前提条件外界因素进行打桩,来判断最终结果是否满足我们的需求
这个时候我们肯定会寻求一些mock
来模拟这些外界条件,可看上面这个代码,我们似乎很难通过mock
来模拟,因为http.post
嵌入在代码之中,具有强耦合性
所以我们需要思考我们应该如何书写代码架构来解决单测问题
面向对象和面向过程#
面向对象和面向过程谁都明白,但是如何用代码表现出两种形式就是重点了
以大象进入冰箱为例:
面向过程
1
2
3
4
5
6
7
8
9func moveToReg(){
elephantWalk()
RegOpen()
elephantIn()
RegClose()
}面向过程顾名思义是针对过程来撰写代码的,通过一个个函数来代表一个一个过程
面向对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24type srv interface{
Walk
Cry
}
type elephant struct {
name string
Age int
email string
}
func (e *elepthant)Walk(){
...
}
func (e *elephant) Cry(){
}
func moveToReg(){
e := NewElephant()
e.Walk()
}在面向对象中,我们更注重于对象个体,对象有属性和行为,通过两个方面去考虑
第一次重构#
毫无疑问,第一版的代码是面向过程的,那么如果改成面向对象又会怎么样呢
首先我们肯定会对代码进行分层,http.post
的代码逻辑不可能存在于service
层
1 | package client |
1 | package service |
1 | package test |
在上面的代码中使用了面向对象的思维实现,使用了多态的思想
- 首先是
interface
的实现,以前我一直将interface
看作是一种规范,但是上面的代码中,我更愿意将其称作版本
,对于同一个interface
可以有mockService
的版本以及正式环境courseClient
的版本,这就是使用了多态的思想 - 面向对象的实现就是构造出一个
struct
,然后给struct
写上方法,同时用interface来标准规范 - 这里的构造函数返回了
interface
,其实golang是不支持返回接口的 - 这里有个问题:就是我们发现每次我们单测都需要将
courseClient interface
的所有接口都实现一遍才能够实现测试,因此–肯定是interface
的使用问题
Interface#
Go interface generally belong in the package that uses values of the interface type, not the package that implements those values. The implementing package should return concrete types.
1 | // DON NOT DO IT ! |
大体意思就是要把interface
属于使用者,而不是实现者,同时实现的包中应该返回具体的type
第二次重构#
1 | package client |
1 | package service |
1 | package test |
我们将 interface
的实现放到了 service
层,会使得interface
的使用更加灵活
client
作为服务的提供方,不可能每次都提供恰到好处的接口来实现,因此就会造成实现不必要的接口的情况
而将interface
放入使用方的情况下,很多问题就会迎刃而解,更加方便
这样子来看 我的CourseServiceImpl
这个单例他只负责 LearningGo
的行为,而不用满足LearningJAVA
等情况
单测工具#
- gomock
- gomonkey
- assert
- test
- sqlmock