本帖最后由 蓉ZXM 于 2021-5-14 14:28 编辑
B' x! B6 ?0 B/ G8 i9 _! u
. [* K# l. `0 }编者按:在所有的开发测试中,接口测试是必不可少的一项。有效且覆盖完整的接口测试,不仅能保障新功能的开发质量,还能让开发在修改功能逻辑的时候有回归的能力,同时也是能优雅地进行重构的前提。编写接口测试要遵守哪些原则?测试代码的结构应该是什么样的?接口测试有哪些实践技巧?本文分享作者在接口测试领域的实践总结。% \$ G& @3 `# d. T: }8 ~
0 Z ?9 M4 E+ }* D4 m+ ~
一线开发同学,可能都或多或少地造成过线上bug甚至故障,也会遇到这样的场景:某同学在开发某功能的时候重构了代码,造成了线上bug或者故障;在开发某个功能时,发现需要修改公共逻辑,害怕影响到其他功能,非常不雅观地拷贝代码,重新写套单独逻辑来支持。
2 H# U5 @' A/ {& E4 M" f5 Z上面这些情况,都包含了一个关键的问题:无论是功能开发还是逻辑重构,如何来保障代码开发的质量?保障的手段,每个人都知道,就是测试。首先是新功能测试,保障新功能逻辑正确;其次是回归测试,保障原有业务功能逻辑正确。测试的方式,一般是两种,人工测试和自动化测试。随着测试技术和工具的持续发展,人工测试比例逐步降低,被自动化测试逐步替代。自动化测试是可持续和可重复的,甚至是可AI化的。- c6 G4 L0 _1 f$ p2 Q+ L4 t; K
$ \- v* d) }/ x5 }1 K; |8 w
1 z: k# ~3 a) s! h9 J) E4 C9 g
一、测试分层
4 E( @; T1 G; [ u
# S) M! R2 C' f2 c; N
7 V! g1 w2 l* a! K自动化测试自然会采取分层策略,如下图所示:
0 A# J$ D0 {, W) l! L' N1 N7 J! S2 w* I; v- A
( f+ x% q1 u- h+ h8 V) S+ c/ D8 R. n
% w- h% L; {1 y. J4 g) ^. E, b
! u7 |8 Q# w5 l4 a" h6 g* p5 u* p9 y: Z5 \
在一个系统内,自动化测试一般分单元测试、模块测试和接口测试。# ]5 K8 m, ]+ t0 H
1.1 单元测试2 R. x2 h. v! l* a2 _9 ]) x
, p! ]) |8 _2 }/ P3 M
目前我的应用代码基本都是基于Spring框架面向接口这种编程模式,单元测试已被弱化。单元测试的要求基本上是单个类单个方法的测试,在我们当前模式下,编写成本太高。当然,如果是一个工具或者一段比较内聚而又复杂的逻辑(例如算法逻辑),还是应该使用单元测试来保障逻辑的正确性。8 p) B' w( U: H
+ e* ~) E2 @3 Z6 t& N! B' d1 `
# h" j4 W; }2 S3 w5 U- y
4 d4 p1 D; {& z+ L
- ^% x, E6 Z0 V4 s; h1.2 模块测试
- r4 \6 m e9 d3 H& N
& m& M5 R( F, z) k( X在系统比较大、模块比较多的情况下,可以建立模块测试层,保障各模块功能的正确性。不过当前的系统发展趋势是微服务架构,因此模块测试层并非十分必要,可以通过接口测试层来覆盖。
' N2 `- t, D4 d" U0 f! y) _
' t( x8 I8 h0 [+ j9 l4 s9 H) {% ]: ?2 }. K; @
7 C0 P! s+ F6 u5 E( W3 h
, I$ m; ~. [" S3 |
1.3 接口测试
! U# c( q# i, L3 H {0 j# k
% x% ^& S% R) O6 q个人觉得准确来说应该叫入口测试,这一层是从系统入口出发进行集成测试。应用入口通常是HSF(一个分布式RPC服务框架)服务,消息,定时任务。作为开发,测试手段千万条,接口测试不可少。在我们应用的接口测试有效且覆盖完整的情况下,不仅能保障我们新功能的开发质量,还能让我们在修改功能逻辑的时候有回归的能力,同时这也是我们做代码重构的前提。同时,易测性也是代码结构合理的一个指标,如果发现一段代码编写测试脚本困难或者无法测试,那就说明当前代码结构不合理需要重构。接下来,我将主要谈一谈接口测试的有效性。4 @$ H$ S' w: a# o8 S+ A% ]
' R8 t, @; n# g
) G% E w$ H9 _* t6 e# t9 W
) x% ]5 S/ T" [& l. g: n3 {1 s7 f2 @ u8 a7 h. ] H l% Q6 l [ e
二、测试原则
% S3 `7 X+ e8 Q2 V5 i3 U# s/ N( P
: H; \) P4 [+ l# D+ O% t1 N2 O8 E* e/ W3 B: U) s
下面是接口测试的基本原则:
3 f* i q' y+ I5 H# h# l
+ j. v7 a( O" \; m" I& S- ]- 自动化:接口测试是非交互式的自动化执行,不需要人参与。
- 独立性:接口测试之间不应该相互依赖。
- 可重复:接口测试可重复执行,不受环境影响。
- BCDE原则,保障接口交付质量。* `. {) d7 m" U/ X
- Border:边界测试
- Correct:正确的输入,正确的预期输出
- Design:按照需求和设计文档编写测试逻辑
- Error:错误输入,预期输出7 k1 e, w! z& S5 B
- 数据准备:数据准备通过系统服务进行,不能通过直接插入db方式。
- 可测性:对于不可测的代码需要进行重构成合理的结构。
- 覆盖性:接口测试需要覆盖所有UC,同时代码覆盖率和分支覆盖率应达到一定标准,新增代码必须被覆盖。
- 持续性:如果代码修改导致已有接口测试执行失败,必须修复代码问题或者测试代码逻辑。
- 时间要求:接口测试应该在项目发布之前完成,不应放到项目发布之后补充。
: ^5 i& f& I3 X6 o% x z % G2 y; X& ?9 Z2 c
9 ^- @' J" t# ?/ C3 W) @& K" l
以上的基本原则应适用于所有层的自动化测试用例,在编写接口测试时,除了上面这些原则,还有其他原则需要遵守,先看一张图:& [2 V0 n0 |5 t6 C3 p4 e3 S
/ j* ^+ F1 N0 H
# ~! I5 [, K9 O6 J
$ Q& W' t$ Y7 @; ]+ V8 L: c+ n W1 I b3 ^/ r# W
9 [/ x) D1 r" G* G) b" I+ p; d) s% ^+ O
' {: B1 H( ~7 J
从系统角度来分析入口调用,以HSF服务为例:
. W( c7 k9 m) e- f# B1 J% V. t; M. q7 y( k% u+ B+ B
' {4 n$ P4 @4 y1 h9 X7 U8 b+ _7 ] m- 外围系统调用由我们系统提供的服务。
- 系统执行了一堆代码逻辑,其中包含有分支逻辑。
- 系统执行过程中依赖外部HSF服务,进行了调用,并得到了返回值。
- 系统执行过程中依赖DB查询或者落地了数据,依赖缓存查询或者落地了数据。
- 系统执行过程中对外发送了消息。
- 给上游系统返回HSF执行结果。
7 f9 X. `+ S; ~0 f+ q 6 ^- c+ G. D, \- Y+ K! d# U2 w& {" H! Z
1 ~% O+ e4 N. | V8 \0 ^; e2 D9 p0 T$ ]) o. K8 W+ W/ V- V! E
& _' z6 s% E1 k& E- V) d
有效接口测试的关键原则是要覆盖所有入口、mock所有依赖、校验执行过程中所留下的痕迹,总结如下:
! E c- ~- B' O" l& `
+ h9 R7 C% ^! M3 T: }! c
" B; {; v) T/ m1 F E: _9 E5 A- 入口覆盖:接口测试用例必须覆盖HSF服务入口、消息入口、定时任务入口。
- 依赖mock:在基本原则中,有可重复这个原则,即接口测试不能受环境依赖,需要mock掉对外依赖。但对于db依赖,不建议完全mock掉,一方面mock成本高,另外可能覆盖不到sql和表约束逻辑。
- 校验完整:有效的接口测试,应该具备完整的校验,没有校验的接口测试是没有意义的。只要执行过程中,留下的痕迹对业务有影响,都要进行完整校验,方能保障接口测试的有效性。
2 g3 }( O M2 ]3 Z* a
- HSF接口返回值校验:按照场景和接口约定进行HSF返回参数校验。
- DB校验:校验落地数据的正确性。
- 缓存校验:校验存入缓存中数据的正确性。
- HSF依赖入参校验:通过mock工具获得依赖HSF调用的入参,进行入参校验。
- 消息校验:通过mock工具获得发送的消息对象,进行消息体校验。4 z7 h1 z$ C6 _) W$ h+ ~
* B4 x5 F; _" |' v L% ^( Y/ b
4 W, ?7 W3 L+ J% O6 V$ [
! O; {% H7 Y2 h: h8 M- S, L+ t! s
. s/ J/ w; s- M) O
/ V, f4 c4 a0 J/ D* }三、测试代码结构
k5 U5 g' \) z1 f) t& y( k
6 V% D" e) F/ M0 v1 Z$ I- y- h% f: @* u B
在编写测试代码的时候,也应跟写业务代码一样,考虑代码的可读、可扩展、可复用性。同时也可以根据系统的业务特性,在测试框架的基础上封装适合当前系统的测试组件,提高测试代码编写效率,规范测试代码结构。
2 Y- x! t# }0 A. p: C5 p8 g- n6 n# R# m
5 N# {8 B$ A6 Z/ k一个接口的测试代码,大概的结构如下。
, s; o- ?' [1 A& A
6 {8 @7 M3 M2 Z3.1 测试准备6 W. E8 y8 b5 V# t9 r! P, T
- t' l, r( f+ w2 v! T- g$ Q# D* Y8 n) L" v. b6 L& U0 c6 J
& j: O1 E: l+ v2 O
. E T: O* v4 G s- W! `3 e% |% B' z2 b- {! ?) o
3.1.1 依赖数据8 [: |/ M& U7 b6 O% c; P$ N! d
% E* f( ?* h- z3 r B0 y
. N+ h' A& E! i& V# l! L
' }$ L: \: o8 M. c& s) Z
/ G/ T' \' C3 _5 E, H
很多时候,我们的测试有数据依赖,可能是配置数据,也有可能是业务数据(例如退款需要依赖支付数据)。- 配置数据:可以通过定义配置文件来初始化配置。
- 业务数据:这类数据,禁止通过直接插入数据方式产生,而是应通过调用业务服务产生。
- , u/ r- ^: A8 p2 S x4 ^8 C L
& b" S$ m, @1 Q1 T, a9 w ) U1 D! Q4 Z! _# \7 \ W& M
# K+ {1 E9 v, H+ i* j2 k3.1.2 依赖mock
1 d% T% ^5 w% k* i; @
) N+ i" @* R5 W/ d1 P" `, r; G$ T; |' H
$ H. \- E! o3 r$ }' h- C
$ l) P( a( ]1 I/ c& J7 r对于外部依赖,需要对被依赖的服务进行mock,避免真实调用。
# j2 u6 a4 B4 `8 g$ V% f: S* ~2 t$ |& t7 `
; I/ C% l+ E! X
+ k7 R$ @2 V5 y" L' V
1 g; V6 C9 }7 S; u* f$ h" ^' n% Q3.1.3 接口测试入参准备' L1 g+ J7 W) y; e( f7 |7 t
. G+ @' c m( J5 x
+ F+ u- U: Z; P
" w1 \$ {. j, P) q) Q* d- t1 U0 L准备接口方面的入参。
, O. m; d2 S# d: K; W: g/ i1 O- J" @5 }0 _! j5 w' e, G8 {
! \! f6 Q: }5 A9 {3 P
! T7 V) \) t. o" T% I3 E4 t3 T& M5 I% f3 \
3.2 测试执行
* T$ n* A- N5 d& {5 f& |/ m" W# P X8 F7 z
4 Y0 y0 z7 W# _& _: ]
% a4 Z' ^) o# C* H+ c
调用接口方法,执行业务逻辑。
4 m1 J H( F, o5 I7 S M) O' c; s8 v1 {
- I% T5 M4 C( M! Q5 d! U4 ^$ `$ S. U( ]2 Y8 a5 i
8 H8 l8 h$ }( J0 E3.3 测试校验
, c7 {: Q. Q* V; j) Q
6 J8 i3 k; P9 o: x' ]! q9 i! W
9 J) r4 C: ?5 r* b5 T/ g- 返回参数校验:校验接口的返回参数。
- DB:校验DB落地数据。
- 缓存数据校验:校验落地到缓存中的数据。
- 消息校验:校验对外发送的消息对象。
- 对外HSF调用校验:校验对外HSF调用的入参。
6 d# |/ a) Z8 e9 K9 n5 _
) C9 }" o" y. P7 a! m) r7 J- n p& x8 h$ |$ `0 E( V
$ w7 f4 z9 h6 l1 b/ E. P( M% a四、实践技巧 9 G: o1 S9 h" V8 y4 ~& b+ G
* J- H( o: R+ x9 {: e8 j5 n4 R+ x
# x; P. b) t5 m
4.1 执行效率
4 O$ E( \' L. C4 H+ a0 m
) @/ B' L6 [! ~# ^. Z# W对于接口测试,执行效率是不得不关注的一个点,若一个接口测试执行3分钟以上才能看到结果,会大大降低开发同学编写接口测试的热情。对于测试执行效率提高,建议的方案为:
! \+ S j8 R J+ \6 ^+ @& T: k6 a, e$ E) c( M% ?7 }1 }# T* y5 ~* s
: R, ?" u. @3 E: E9 O; o+ R- l2 ?2 G4 G
- 最小化启动测试上下文,例如spring boot的应用,启动spring就可以了。
- 使用内存数据库,例如h2。
- 将中间件依赖mock掉。
: b# \2 g" c, \1 s/ d/ g7 b : p5 P2 M$ O) Z
" J; X7 g& t( j
4.2 测试框架选择
3 O) c+ Y2 d- }5 j+ X& ]
9 r0 P. p7 z/ c& b0 R( m
$ _* J6 G& I, {: Z0 U9 z+ v9 w/ e/ i+ H" _# \
对于测试框架,建议选择基于testng,能够提供通过配置文件做数据准备的测试框架。如果找不到合适的,可以自己基于testng进行封装。
9 s; p/ p9 L5 b7 x4 }6 e
0 K* n: U2 e5 ?
% H; E9 H6 q- N
* Z: m/ h* o5 Q8 O
0 `4 z3 ?. c# P( Y+ ~4.3 接口测试覆盖度( h) w0 z Z, F8 c2 Z# l8 {* S
. G9 B% A0 J; b8 C. C* I6 ?
+ G6 X5 c% \! {. d5 b: S* o8 V/ d; H! h" Y
场景的完整性影响着测试用例的覆盖度:
; j3 {- S7 \! P, ~
1 C3 v9 {+ I/ j. B" b, G7 h; ?& e* W$ ~- c7 h" \* P3 I
; N+ V, r' [7 h: w' G" {
; K% J$ ?9 m1 W0 t% B9 A; o
( E0 y+ k$ V; l- 一方面需要开发同学基于业务场景的输入和测试经验枚举出正常和异常情况。
- 另一方面接口方法也有一些固定需要测试的点,例如幂等测试,边界值测试,参数不正确测试等等。- W4 C# C0 B' b
同时也要通过覆盖率工具查看接口未覆盖的代码或分支逻辑,进行针对性的场景覆盖测试。根据我的经验,分支完整覆盖非常重要,特别是异常的分支。
! X* a$ j6 c) }2 _8 b. Y/ ]$ }! Q. l- |. |% z
4 W, O! ^9 ]- p1 s( `- o. Z* c( ] u* w( G" H U+ g
& ?4 F8 j' j3 `5 M6 w8 s/ d
) R7 I' h4 u" O/ q7 y
+ r5 {+ L" r5 [# O# M7 ]5 N
) d: G2 ]0 H5 }* ~' `& B: R8 G# U0 \& @0 v) \ Y# J
9 r, M9 R. q- h6 r! o& Z. o& t# H+ j4 O- n) h O
# ^+ w7 B8 t2 B7 c$ N
要保障系统线上运行稳定,质量保障手段必不可少。虽然现在有很多自动化的保障手段,但接口测试依然是最基本的和最重要的保障手段之一。如能做到持续保障接口测试覆盖度和有效性,很大程度上会降低线上bug的产生,开发同学也会更有积极性去重构代码。(IDCF)/ t, m0 l: @% C* N
6 j- [" r8 r# e/ K
|