Go应用单元测试实践

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: Go应用单元测试搭建

背景

高德打车运营的应用大多基于go进行开发的,我们希望在预集成环境下,当研发部署完代码,能自动触发单元测试和接口自动化测试,并生成覆盖率报告。参考了许多篇关于go单元测试的文章,有的缺少行增量覆盖率,有的缺少case运行结果/case运行日志。本文旨在搭建一个稳定运行且维护成本低的单元测试/集成测试环境。

一、单元测试

1.1 单测运行概述

图1 单测运行流程图

aone作为阿里巴巴集团数字化研发协同平台,本身提供了各种集成测试实验室,实验室中可以运行自定义脚本。如图1所示,为单元测试运行流程图。单元测试由aone实验室脚本触发,Java服务收到单测任务后调起单测脚本并执行,最后由aone实验室轮询运行结果。之所以不在单测实验室脚本中直接运行单测,主要存在以下两个原因。一是单测的运行依赖GO环境,以及一些生成覆盖率文件所需的三方工具。目前aone实验室不支持自定义镜像接入,每次运行都需要安装环境,安装环境的耗时远大于运行单测。二是每个应用的单测运行命令可能不太一样,一旦应用数目较多,如果单测脚本需要调整,更改的成本比较高。因此启动一个JAVA服务(完全可以复用已有的服务,降低成本),将运行单测所需要的脚本,以及环境都打包在这个服务上。aone上的实验室脚本,只进行单测任务的下发、轮询和运行结果的展示。具体流程如下:

  1. 当开发在预集成环境提交代码、部署完成之后,流程自动运行单测实验室。单测实验室里的脚本,先调用任务下发接口/unit/taskReceive,这时Java服务会调用对应的单测脚本。
  2. 由于单测脚本运行时间会比较长,所以/unit/taskReceive接口会超时。在单测脚本正在运行的时候,单测实验室的脚本会一直调用/unit/taskQuery接口,查询此次单测任务的状态,直到返回正确结果为止。
  3. 当单测脚本完成时,会回调任务完成接口/unit/taskSave接口,将结果存起来。这样单测实验室脚本再调用/unit/taskQuery接口查询时,就会返回此次单测的结果。
  4. 单测实验室脚本,根据任务返回的结果,将单测结果解析、展示。

1.2 环境搭建

将所需的环境,打包到Java服务的docker中:

  • golang安装

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。参考地址:https://github.com/axw/gocovhttps://github.com/AlekSi/gocov-xml

go get github.com/matm/gocov-html
go get github.com/axw/gocov/... 
go get github.com/AlekSi/gocov-xml
  • 行增量覆盖率工具安装

利用diff-cover(https://github.com/Bachmann1234/diff_cover),生成行增量覆盖率。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安装&配置

运行单元测试时,依赖开发的代码。需要配置好一个有代码权限的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

1.3 Java服务实现

1.3.1 单测任务下发接口

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 "************************************" >> $UNIT_TEST_RESULT_FILE
cat $LOG_FILE | grep "\--- FAIL: " >> $UNIT_TEST_RESULT_FILE
echo "************************************" >> $UNITTEST_RESULT_FILE
echo "SUCCESS:" >> $UNIT_TEST_RESULT_FILE
cat $LOG_FILE | grep "\--- PASS: " >> $UNIT_TEST_RESULT_FILE
echo "************************************" >> $UNIT_TEST_RESULT_FILE
iconv  -f UTF-8 -t gbk $UNIT_TEST_RESULT_FILE > temp.txt
sed -i 's/    //g;s/---//g' temp.txt
cat temp.txt > $UNIT_TEST_RESULT_FILE
cp $UNIT_TEST_RESULT_FILE /${your path}/res_unit/${PREFIX}pass.txt
#结果收集
curl -i "http://${your server host}/unit/taskSave" -H "Content-Type:application/json" -X POST -d "{\"taskId\":\"$TaskId\", \"appName\":\"$APP_NAME\", \"branch\": \"$Branch\", \"taskRes\": \"{\\\"code_coverage_update_lines_total\\\":$CODE_COVERAGE_UPDATE_LINES_TOTAL, \\\"code_coverage_update_lines_cover\\\":$CODE_COVERAGE_UPDATE_LINES_COVER,\\\"code_coverage_lines_total\\\":$CODE_COVERAGE_LINES_TOTAL, \\\"code_coverage_lines_cover\\\":$CODE_COVERAGE_LINES_COVER, \\\"fail\\\":$fail, \\\"pass\\\":$pass}\"}"

1.3.2 单测任务查询接口

PATH:/unit/taskQuery
METHOD:POST
Params:{
    "taskId": "123456", //可以用日期20220221102104,主要用来标识此次单测
    "appName":"xxxx", //应用名,根据应用名,选择运行对应的单测脚本
}
Result:如果单测运行完成,返回code="1",data是单测结果。如果单测没完成,返回code="2",data="task ongoing",如果单测运行超过10分钟,返回code="2",data="redis nil or delay"

具体实现逻辑:直接查询redis中key="${appName}${taskId}-unit"对应的值即可。

1.3.3 单测结果保存接口

PATH:/unit/taskSave
METHOD:POST
Params:{
    "taskId": "123456", //可以用日期20220221102104,主要用来标识此次单测
    "appName":"xxxx", //应用名,根据应用名,选择运行对应的单测脚本。
    "taskRes":"{\"code_coverage_update_lines_total\":100,\"code_coverage_update_lines_cover\":100,\"code_coverage_lines_cover\":100,\"code_coverage_lines_total\":100,\"fail\":0,\"pass\":100}" //单测运行结果
}
Result:成功返回code="1"

1.4 实验室配置

如1.1所述,aone实验室只需要分发任务、轮询任务,以及解析结果。

TASK_ID=$(date "+%Y%m%d%H%M%S")
APP_NAME=`xxxx`
PREFIX=$APP_NAME$TASK_ID
echo $TASK_ID
echo $APP_NAME
echo $PREFIX
failed="true" 
# 分发任务
curl -i "http://${your server host}/unit/taskReceive" -X POST -H "Content-Type:application/json" -d "{\"taskId\": \"$TASK_ID\",\"appName\": \"$APP_NAME\", \"branch\": \"${branch}\", \"repo\":\"${repo}\"}"
for time in 10s 30s 40s 50s 70s 100s 100s 70s 50s 40s 30s 10s
do
    #轮询任务
    res=$(curl "http://${your server host}/unit/taskQuery" -X POST -H "Content-Type:application/json" -d "{\"taskId\": \"$TASK_ID\",\"appName\": \"$APP_NAME\", \"branch\": \"${branch}\", \"repo\":\"${repo}\"}")
    echo $res
    code=$(echo $res | grep -o -E 'code":[0-9]' | cut -d ":" -f2)
    isOngoing=$(echo $res | grep -o -E 'data":[^}]*' | cut -d ":" -f2)
    if [ "$code" = "1" ] && [ $isOngoing != "\"ongoing\"" ] && [ $isOngoing != "null" ]
    then
      #根据res解析单元测试运行结果
      #略
      break
    fi
    sleep $time
done
if [ "$failed" == "true" ]
then
    echo "Job failed"
fi

1.5 最终结果

最终的运行结果如图2,单元测试、行增量覆盖率、行覆盖率都可以点击跳转查看详情。如图3,4,5。跳转地址的实现,是采用nginx提供的访问静态文件功能。只需要在nginx的配置文件中,增加配置

location ^~ /res_unit {
  root  /${your path};
}

这样,如果想访问a.html文件,只需要将其放在/${your path}/res_unit/a.html。就可以通过链接https://${your server host}/res_unit/a.html访问到。

图2 aone单测运行示例                                                            图3 case通过情况                    

图4 行增量覆盖率

图5 行覆盖率

其他

高德共享出行技术质量团队求贤若渴(北京岗),诚招Java开发P6&P7、测试开发工程师P6&P7,有意向欢迎联系ling.wanglingfeng@alibaba-inc.com

相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
相关文章
|
28天前
|
数据采集 监控 机器人
浅谈网页端IM技术及相关测试方法实践(包括WebSocket性能测试)
最开始转转的客服系统体系如IM、工单以及机器人等都是使用第三方的产品。但第三方产品对于转转的业务,以及客服的效率等都产生了诸多限制,所以我们决定自研替换第三方系统。下面主要分享一下网页端IM技术及相关测试方法,我们先从了解IM系统和WebSocket开始。
45 4
|
8天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
44 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
30天前
|
人工智能 JavaScript 前端开发
自动化测试框架的演进与实践###
本文深入探讨了自动化测试框架从诞生至今的发展历程,重点分析了当前主流框架的优势与局限性,并结合实际案例,阐述了如何根据项目需求选择合适的自动化测试策略。文章还展望了未来自动化测试领域的技术趋势,为读者提供了宝贵的实践经验和前瞻性思考。 ###
|
1月前
|
Java 测试技术 数据安全/隐私保护
软件测试中的自动化策略与工具应用
在软件开发的快速迭代中,自动化测试以其高效、稳定的特点成为了质量保证的重要手段。本文将深入探讨自动化测试的核心概念、常见工具的应用,以及如何设计有效的自动化测试策略,旨在为读者提供一套完整的自动化测试解决方案,帮助团队提升测试效率和软件质量。
|
2月前
|
安全 Serverless Go
Go语言中的并发编程:深入理解与实践####
本文旨在为读者提供一个关于Go语言并发编程的全面指南。我们将从并发的基本概念讲起,逐步深入到Go语言特有的goroutine和channel机制,探讨它们如何简化多线程编程的复杂性。通过实例演示和代码分析,本文将揭示Go语言在处理并发任务时的优势,以及如何在实际项目中高效利用这些特性来提升性能和响应速度。无论你是Go语言的初学者还是有一定经验的开发者,本文都将为你提供有价值的见解和实用的技巧。 ####
|
28天前
|
测试技术 Python
探索软件测试的深度与广度:从理论到实践
在数字化时代,软件已成为我们生活中不可或缺的一部分。随着技术的不断进步和用户需求的多样化,确保软件质量变得尤为重要。本文将深入浅出地介绍软件测试的核心概念、类型及其在软件开发生命周期中的重要性。我们将通过实际案例,展示如何实施有效的测试策略,并探讨自动化测试的未来趋势,旨在为读者提供一套完整的软件测试知识体系,帮助提升软件质量和开发效率。
|
29天前
|
测试技术 Python
探索软件测试的奥秘:从理论到实践
在软件开发的宇宙中,软件测试犹如一颗璀璨的星辰,指引着质量的方向。本文将带你穿梭于软件测试的理论与实践之间,揭示其内在的逻辑和魅力。从测试的重要性出发,我们将探讨不同类型的测试方法,并通过实际案例分析,深入理解测试用例的设计和应用。最后,我们将通过一个代码示例,展示如何将理论知识转化为实际操作,确保软件质量的同时,也提升你的测试技能。让我们一起踏上这段探索之旅,发现软件测试的无限可能。
|
1月前
|
jenkins 测试技术 持续交付
自动化测试框架的搭建与实践
在软件开发领域,自动化测试是提升开发效率、确保软件质量的关键手段。本文将引导读者理解自动化测试的重要性,并介绍如何搭建一个基本的自动化测试框架。通过具体示例和步骤,我们将探索如何有效实施自动化测试策略,以实现软件开发流程的优化。
70 7
|
1月前
|
测试技术
探索软件测试的奥秘:从理论到实践
本文深入探讨了软件测试的基本概念、重要性、主要类型以及实施策略。通过分析不同测试阶段和相应的测试方法,文章旨在为读者提供一套完整的软件测试知识体系,帮助他们更好地理解和应用测试技术,确保软件产品的质量和可靠性。
61 4
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
智能化软件测试:AI驱动的自动化测试策略与实践####
本文深入探讨了人工智能(AI)在软件测试领域的创新应用,通过分析AI技术如何优化测试流程、提升测试效率及质量,阐述了智能化软件测试的核心价值。文章首先概述了传统软件测试面临的挑战,随后详细介绍了AI驱动的自动化测试工具与框架,包括自然语言处理(NLP)、机器学习(ML)算法在缺陷预测、测试用例生成及自动化回归测试中的应用实例。最后,文章展望了智能化软件测试的未来发展趋势,强调了持续学习与适应能力对于保持测试策略有效性的重要性。 ####