发货服务化的工程质量实践
概述###
要构建高质量软件工程,需要构建两个关键步骤:(1)持续自动化的开发与部署流程,能够将开发、测试、部署、验收等重要环节流畅地串联起来,并增强自动化和可靠性;(2)持续增强每个环节的质量。 推荐阅读: 《持续交付:发布可靠软件的系统方法》
为方便起见,发货服务化工程简称为D,项目结构遵循标准 Java Maven 结构。
D
src/main/java
src/main/resources
src/test/java
src/test/resources
pom.xml
持续集成####
代码开发(包括添加相应单测) -> 提交前自动运行单测,单测不通过不能提交 -> 单测全部通过【覆盖率达标】 -> Push到代码库(触发持续集成环境单测全部通过) -> 代码Reivew通过 -> 成功构建和部署到QA环境 -> 自动触发QA验收测试(接口测试与业务场景测试) -> QA验收测试全部通过 -> 成功构建和部署到预发环境 -> 预发验收测试通过 -> 发布到正式生产环境 -> 生产环境验收通过。
代码开发###
代码开发应该注意两个基本点:(1) 可复用性; (2) 将潜在变化与不变分离出来,适当使用设计模式。
代码开发可适当考虑 TDD 开发。 对于提交结果而言, 新增方法一定要添加相应单测。 最好能有自动检测是否有相应单测增加工具。
单元测试###
更好地编写单测####
参考: 深入探究单测编写
提交前自动运行单测####
开发同学常常忘了运行单测, 其实只要一个小技巧即可:在 ~/.bash_profile 文件末尾增加一行 alias gp="mvn test && git push origin" 。 当运行 gp 分支名时, 会自动运行单测, 单测不通过是不能提交的。
单测覆盖率####
pom.xml 里配置单测覆盖率maven插件, 然后在项目根目录下运行
mvn clean && mvn cobertura:cobertura && cat /target/site/cobertura/coverage.xml | grep "<coverage"
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
<configuration>
<formats>
<format>xml</format>
</formats>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>cobertura</goal>
</goals>
</execution>
</executions>
</plugin>
这是发货服务化工程的单测覆盖率结果:
即使核心代码单测覆盖率达到100%,从整体上也难以达到指定指标, 这主要是由于一些“无需测试的方法比如getter/setter”的存在,以及一些 utils , 缺乏单测的复杂业务方法的存在。如何达到指定单测率而且保证质量呢? 首先建立若干准则:
(1) 核心主流程经过的所有方法的行覆盖率应该达到 100%;
(2) 非核心主流程的方法,只要程序会运行到且有一定逻辑,行覆盖率 90%;
(3) Utils 的未用到的方法,要么删除,要么加上单测;
(4) 无需测试的方法, 可以生成单测允许通过,但不必过多考虑。
查看单测覆盖率报告,看看是哪些地方覆盖率低,重要性如何。 在重要性相对较高、覆盖率低的地方添加单测。
代码Review###
在持续集成环境中, 当代码 push 到代码库后,应当自动触发持续集成环境的单元测试(防止开发同学本地忘了运行单测), 单测不通过将在 CodeReview 里增加一个明显的错误标识。一般单测不通过是不允许进入人工代码 Review 的。
在代码库工具平台上创建代码Review , 并将链接发送到相关Review同学。根据代码Review 意见改善代码后提交,直到单测全部通过、代码Review全部通过后进入下一步。
QA验收测试###
构建和部署到QA测试环境####
理想情况下, 当 git push 到代码库后,应当自动触发发布系统将指定分支的代码部署到QA环境,然后运行接口测试用例。
验收测试####
验收测试主要是针对业务场景的测试。可以包括单接口测试和组合接口测试。由于发货是单个接口完成,因此,可以只验收单接口测试。单接口测试必须覆盖各种场景的发货。比如普通单商品发货、普通多商品发货、送礼发货、分销发货、批量发货、有退款维权的发货、非合法发货返回的错误码。
运行接口测试用例####
发货服务化的接口测试用例放在工程 service-test 的 DeliveryServiceDubboTest 和 DeliveryServiceHttpTest 测试类里。 方便起见, 最好将 service-test 放置在与 java 工程同级目录下,即 work/java/D, work/java/service-test。先将环境切换到 QA host ; 然后在 service-test 项目根目录下, 运行 mvn test -Dtest=DeliveryService*Test 即可。
预发布及验收测试###
在发布系统选择预发布, 部署工程到预发布环境; 然后在测试店铺验证普通发货、送礼发货、分销发货、批量发货均正常。
正式发布及验收测试###
在发布系统选择正式发布, 部署工程到正式环境; 然后登录测试店铺验证普通发货、送礼发货、分销发货、批量发货均正常。
一个简单的集成脚本###
在推送代码到代码库时使用: ./push.sh 分支名
#!/bin/bash
#-----------------------------------------
# add line below to ~/.bash_profile
# alias gp="mvn test && git push origin "
#------------------------------------------
alias gp="mvn clean test && git push origin "
shopt -s expand_aliases
if [ x$1 != x ]
then
echo "git push origin $1"
gp $1
else
gp
fi
echo "code review passed ?"
read answer
if [ "$answer"x != "y"x -a "$answer"x != "Y"x ]
then
echo "code review not pass, please fix and recommit"
exit 1
fi
echo "deploy to qa ?"
read answer
if [ "$answer"x != "y"x -a "$answer" != "Y"x ]
then
echo "not deploy to qa not pass, please fix and recommit"
exit 1
fi
# set qa enviroment
echo "switch qa environment."
sudo python ./tools/switchhost.py qa
echo "run interface tests."
cd ../service-test
mvn test -Dtest=DeliveryService*Test
使用 SwitchHost 工具时自动切换 HOST 的脚本
#!/usr/bin/python
#_*_encoding:utf-8_*_
#------------------------------------------------------
# switchhost.py automatically switch hosts managed by SwitchHosts tools
# usages: sudo python switchhost.py [qa] [q] [local] [l] [pre] [p] [online] [o] [c]
#------------------------------------------------------
import json
import os
import sys
homedir = os.environ['HOME']
hostcodemap = {"local": "My Hosts", "l": "My Hosts", "qa": "qa", "q": "qa", "online":"online", "o": "online", "pre":"pre", "p": "pre", "c": "camen-dev"}
def switch(env):
with open(homedir+'/.Switchhosts/data.json') as data_file:
hostobj = json.load(data_file)
hostmap = {}
for host in hostobj['list']:
hostmap[host['title']] = host['content']
content = hostmap[env]
with open('/etc/hosts', 'w') as hostfile:
hostfile.write(content)
hostfile.close()
def getEnv():
if len(sys.argv) == 1:
return "online"
if len(sys.argv) > 1:
env = sys.argv[1]
env = hostcodemap[env]
if env is None:
env = "online"
return env
if __name__ == '__main__':
switch(getEnv())
发货服务化的分流心得###
严格代码质量####
使用设计模式确保代码组织的可扩展性, 并严格进行代码 Review 。
- 使用模板方法模式,确保通用的发货流程模板,以及不同的发货流程之间的解耦(普通发货、送礼发货、分销发货、批量发货);
- 确保业务方法只做一件事;
双重护航测试####
QA 提测之前,编写发货工程的单测及服务接口的可重复自动化测试用例(检测到数据库字段级别)尽可能覆盖到业务流程、方法及工具类,并确保全部通过。双重护航保证每一行代码改动都能快速回归有效;开发必须对自己的代码质量负最主要责任;
发布谨慎平滑####
分流步骤严格循序渐进,按“除TOP20外 0.001, 0.005, 0.01, 0.05, 0.1, 0.3, 0.5, 0.7, 1.0, TOP20加入”平滑进行,切忌贪急求快。每一次发布要谨慎:
- 登录服务器查看启动日志service.log , 确保发布后是服务正常启动的;
- 到测试店铺进行验证发货是否正常;
- 查看发货日志是否正常;查看监控是否正常。
变更回归测试####
- 每一行代码改动,都必须运行单测及接口自动测试用例,保证全部通过;
- 每个BUG修复,都要增加相应的单测和接口测试用例覆盖;
- 分流接口也要增加接口测试用例,并同步运行测试用例;
全天覆盖监控####
打开Grafana服务器监控、日志平台与服务接口监控平台,全天候不定时刷新日志和监控曲线:
- 关注CPU/内存/IO/网络流量是否正常及报警;
- 评估和解决发货日志其中出现的错误和警告;
- 关注发货接口的调用次数和失败次数。
日志分级统计####
日志分级,便于排查真正的系统问题。
- 非系统级别,比如参数不合法等,使用警告级别,不必加入错误统计中;
- 系统错误,必须使用错误级别,加入错误统计;
局部升级####
若在分流中需要模块升级、字段升级、配置改动等,尽量保持小的改动(否则要进行全量回归测试);协调好发布顺序和时机;控制好服务工程与外部依赖的边界,保持边界兼容即可;
不放过测试用例的失败####
当服务接口的可重复自动化测试用例运行失败,要仔细排查问题的原因所在。在common-model升级后,发现发货测试用例运行失败,经仔细排查后,发现有两个代码上的小问题,存在潜在风险,并及时修复了这两个问题。
不放过Error日志和Warn日志####
Error 日志通常是系统有潜在问题的表现, 比如排查一个发货没有插入发货数据库表的问题,就发现商品传空的时候就有问题,甚至发现之前的事务引用修复分支并没有合并到master 分支发布(发布遗漏); Warn 日志通常是用户使用不当导致的, 就需要跟商家进行沟通,停掉这种无用的浪费资源的行为。