高德打车运营的单元应用大多基于go进行开发的,我们希望在预集成环境下,测试当研发部署完代码,实践能自动触发单元测试和接口自动化测试,应用并生成覆盖率报告。单元参考了许多篇关于go单元测试的测试文章,有的实践缺少行增量覆盖率,有的应用缺少case运行结果/case运行日志。 本文旨在搭建一个稳定运行且维护成本低的单元单元测试/集成测试环境。 图1 单测运行流程图 aone作为阿里巴巴集团数字化研发协同平台,实践本身提供了各种集成测试实验室,应用实验室中可以运行自定义脚本。单元如图1所示,测试为单元测试运行流程图。单元测试由aone实验室脚本触发,Java服务收到单测任务后调起单测脚本并执行,最后由aone实验室轮询运行结果。之所以不在单测实验室脚本中直接运行单测,主要存在以下两个原因。一是单测的运行依赖GO环境,以及一些生成覆盖率文件所需的三方工具。目前aone实验室不支持自定义镜像接入,每次运行都需要安装环境,亿华云安装环境的耗时远大于运行单测。二是每个应用的单测运行命令可能不太一样,一旦应用数目较多,如果单测脚本需要调整,更改的成本比较高。因此启动一个JAVA服务(完全可以复用已有的服务,降低成本),将运行单测所需要的脚本,以及环境都打包在这个服务上。aone上的实验室脚本,只进行单测任务的下发、轮询和运行结果的展示。具体流程如下: 将所需的环境,打包到Java服务的docker中: go单测需要运行go test,所以需要在环境中安装go。安装完成后,配置环境变量和代理。 wget https://golang.google.cn/dl/go1.17.8.linux-amd64.tar.gz tar -zxvf go1.17.8.linux-amd64.tar.gz -C /usr/local/ mkdir -p /${ your go path dir}/gopath echo -e "export PATH=\"$PATH:/usr/local/go/bin:/${ your go path dir}/gopath/bin\"\nexport GOPATH=\"/${ your go path dir}/gopath\"\nexport GOPROXY=\"${ go代理地址},direct\"" >> /etc/profile source /etc/profile 运用一些开源工具,将单测生成的覆盖文件转换成xml/html格式的覆盖率文件。主要用到gocov-html,gocov,gocov-xml。参考地址[1][2]。 go get github.com/matm/gocov-html go get github.com/axw/gocov/... go get github.com/AlekSi/gocov-xml 利用diff-cover[3],生成行增量覆盖率。云服务器提供商diff-cover依赖python3,python3的安装可能需要先装好gcc,automake,autoconf,libtool,make,zlib,zlib-devel openssl。 yum -y install gcc automake autoconf libtool make zlib zlib-devel openssl openssl-devel wget https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tgz tar -zxvf Python-3.8.1.tgz && cd Python-3.8.1 && ./configure && make && make install pip3 install diff-cover -i https://mirrors.aliyun.com/pypi/simpl 运行单元测试时,依赖开发的代码。需要配置好一个有代码权限的git ssh公钥和私钥,用来下载代码。 yum -y git name=`git config user.name` if [ -z "$name" ] then git config --global user.name "xxx" git config --global user.email "xxxx@xxxx.xxxx.com" mkdir -p ~/.ssh cp ${ your id_rsa} ~/.ssh/ fi 单测任务下发接口 Path:/unit/taskReceive Method:POST Params:{ "taskId": "123456", //可以用日期20220221102104,主要用来标识此次单测 "appName":"应用A", //应用名,根据应用名,选择运行对应的单测脚本。比如应用A就会运行应用A.sh "branch":"releases/test-branch-code", //需要运行单测的分支名 "repo":"git@xxxxx.git" //应用A的代码地址,下载代码之后,才能运行单测 } Result:返回啥都行,反正会超时。 具体实现逻辑: 在redis中记录此次单测任务,key:"${ appName}${ taskId}-unit",value:"ongoing"。以便/unit/taskQuery查询,从而知道单测还在运行中。根据appName参数,选择执行${ appName}.sh脚本。如果脚本不存在,就去阿里云对象存储服务(Object Storage Service,简称OSS)下载脚本(所以,如果单测脚本有更新,就更新下OSS上的脚本,然后删除运行机器上的${ appName}.sh即可。这样可以不重新部署Java服务,即可更改运行脚本)。${ appName}.sh脚本大致逻辑如下:source /etc/profile APP_NAME=$1 Branch=$2 TaskId=$3 Repo=$4 DIR=`pwd` PREFIX=$APP_NAME$TaskId #生成覆盖率文件的文件夹 mkdir -p $DIR/$APP_NAME/$TaskId/cover COVER_FILE=$DIR/$APP_NAME/$TaskId/cover/core.cover LOG_FILE=$DIR/$APP_NAME/$TaskId/cover/log.txt COVER_DIR=$DIR/$APP_NAME/$TaskId/cover UNIT_TEST_RESULT_FILE=$DIR/$APP_NAME/$TaskId/cover/unit_pass.txt #存放覆盖率详情html文件的文件夹 mkdir -p /${ your path}/res_unit #下载代码 cd $DIR/$APP_NAME/$TaskId git clone -b $Branch $Repo #运行单元测试 cd ./$APP_NAME CONF_DIR=$DIR/$APP_NAME/$TaskId/$APP_NAME/conf go test ./... -timeout 3m -v -gcflags=-l -cover=true -coverprofile=$COVER_FILE -mod=vendor -args --confDir=$CONF_DIR >> $LOG_FILE #行增量覆盖率 gocov convert $COVER_FILE | gocov-xml > $COVER_DIR/coverage.xml diff-cover $COVER_DIR/coverage.xml --compare-branch=origin/master --html-report $COVER_DIR/report.html > $COVER_DIR/diff.out tmp=`cat $COVER_DIR/diff.out | grep "Total:" | cut -d : -f2` if [ -n "$tmp" ] then echo "CODE_COVERAGE_NAME_UPDATELINES : 行增量" CODE_COVERAGE_UPDATE_LINES_TOTAL=`cat $COVER_DIR/diff.out | grep "Total:" | cut -d: -f2 | grep -o -E [0-9]+` miss=`cat $COVER_DIR/diff.out | grep "Missing:" | cut -d : -f2 | grep -o -E [0-9]+` CODE_COVERAGE_UPDATE_LINES_COVER=$(( CODE_COVERAGE_UPDATE_LINES_TOTAL - miss)) fi cp $COVER_DIR/report.html /${ your path}/res_unit/${ PREFIX}update.html #代码行覆盖率 gocov convert $COVER_FILE | gocov-html > $COVER_DIR/line.html CODE_COVERAGE_LINES_COVER=`head -n 50 $COVER_DIR/coverage.xml | grep "lines-valid" | awk -F lines-covered { print $2} | awk -F { print $1} | grep -o -E [0-9]+` CODE_COVERAGE_LINES_TOTAL=`head -n 50 $COVER_DIR/coverage.xml | grep "lines-valid" | awk -F lines-valid { print $2} | awk -F { print $1} | grep -o -E [0-9]+` cp $COVER_DIR/line.html /${ your path}/res_unit/${ PREFIX}line.html #case 通过情况 pass=`cat $LOG_FILE | grep -o "\--- PASS: " | wc -l` fail=`cat $LOG_FILE | grep -o "\--- FAIL: " | wc -l` echo "一、应用背景
二、测试单元测试
1.单测运行概述