持续交付之.Net项目单测自动化框架落地(入门篇)_持续交付


前言

自动化测试框架和平台形形色色,只有最合适项目团队的才是最好的,本文带着快速搭建一个属于自己.Net项目的轻型单测自动化框架

落地方案

技术架构

持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_02

主要特点

  • 使用 MSTest 作为项目运行框架,方便执行测试用例,生成测试结果
  • 使用开源工具作为报告驱动,二次美化功能,界面更美观,内容清晰,实现在线 HtmlReport
  • 实现测试历史结果趋势分析
  • 实现钉钉自动化通知及跳转功能
  • 实现覆盖度结果上传 SonarQube
  • 使用 Jenkins 作为自助式测试,一站式测试平台,方便自动编译,自动运行测试脚本,发送测试报告,通知等

技术选型

  • 单测框架:MSTest
  • 单测报告框架:Trxer
  • 覆盖度扫描工具:Opencover
  • 覆盖度报告框架:ReportGenerator
  • 覆盖度报告服务:Tomcat
  • 自动通知:钉钉webhook & python
  • Jenkins 插件:
  • Git plugin:拉取代码
  • Version Number Plugin:生成部分版本号
  • window 批处理:执行bat脚本
  • SonarScanner for MSBuild:静态代码扫描
  • MSBuild Plugin:代码编译
  • MSTest plugin:执行测试
  • HTML Publisher plugin:单测在线 HtmlReport
  • Groovy Plugin:设置 HtmlReport 插件 css 生效

相关工具链接:

  • trxer:https://github.com/NivNavick/trxer
  • opencover:https://github.com/OpenCover/opencover
  • ReportGenerator:https://github.com/danielpalme/ReportGenerator

核心步骤

1)设置上传覆盖度结果上传到 SonarQube :


1. /d:propertyKey="TestResults\TestResults.trx"
2. /d:sonar.cs.opencover.reportsPaths="TestResults\CodeCoverageResults.xml"

参考下图: 持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_032)执行 MSBuild 编译,这里参考自己的项目设置: 持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_043)执行单测及覆盖度扫描脚本如下:


1. ::删除原文件
2. rmdir /s/q TestResults
3. mkdir TestResults
4. 
5. ::执行单元测试及覆盖度扫描
6. "C:\opencover.4.7.922\opencover.console.exe" -target:"C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\MSTest.exe" -targetargs:/testcontainer:"D:\Jenkins-workspace\Jenkins\workspace\bin\unitTest\UnitTestServer.dll" -filter:+[*]* -register:user -mergebyhash -output:TestResults\CodeCoverageResults.xml
7. 
8. ::修改文件名
9. cd TestResults
10. ren *.trx TestResults.trx
11. 
12. ::生成单元测试报告
13. "D:\trxer\TrxerConsole\bin\Debug\TrxerConsole.exe" TestResults.trx
14. 
15. ::生成单元测试覆盖度报告
16. "C:\ReportGenerator_4.1.4\net47\ReportGenerator.exe" -reports:"CodeCoverageResults.xml" -targetdir:"Coverage_%BUILD_NUMBER%"
17. 
18. ::拷贝到服务器
19. xcopy Coverage_%BUILD_NUMBER% \\xxx.xxx.xxx.xxx\webapps\v3c\coverage\%JOB_NAME%\Coverage_%BUILD_NUMBER% /I/F/E/Y

参考下图: 持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_05注意:

  • Tomcat 服务的主机需要开启共享文件(window)

4)配置 Groovy script,让 HtmlReport 插件 css 能用,同时不用担心 Jenkins 重启:

  1. System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")

参考下图: 持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_065)构建后操作,发布单测 HtmlReport: 持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_07注意:

  • HTML directory to archive:报告路径
  • Index page[s] :报告索引名称
  • Keep past HTML reports:保留报告

6)设置统计分析测试结果 : 持续交付之.Net项目单测自动化框架落地(入门篇)_持续交付_087)post build task 执行钉钉通知: 持续交付之.Net项目单测自动化框架落地(入门篇)_持续交付_09Python脚本:


1. # coding=utf-8
2. 
3. '''
4. @author: zuozewei
5. @file: notification.py
6. @time: 2019/4/25 18:00
7. @description:dingTalk通知类
8. '''
9. import os, jenkins, configparser, requests, json, time
10. from dingtalkchatbot.chatbot import DingtalkChatbot
11. from jsonpath import jsonpath
12. 
13. # 获取 Jenkins 变量
14. JOB_NAME = str(os.getenv("JOB_NAME"))
15. BUILD_URL = str(os.getenv("BUILD_URL")) + "console"
16. BUILD_VERSION = str(os.getenv("BUILD_VERSION"))
17. JENKINS_HOME = os.getenv("JENKINS_HOME")
18. BUILD_NUMBER = str(os.getenv("BUILD_NUMBER"))
19. WORKSPACE = os.getenv("WORKSPACE")
20. 
21. versionPath = JENKINS_HOME + "\workspace\Version.ini"
22. 
23. config = configparser.ConfigParser()
24. config.read(versionPath)
25. xxx_Major = config.get("xxx", "xxx_Major")
26. xxx_Minor = config.get("xxx", "xxx_Minor")
27. xxx_Build = config.get("xxx", "xxx_Build")
28. xxx_Revision = config.get("xxx", "xxx_Revision")
29. VERSION = xxx_Major + "." + xxx_Minor + "." + xxx_Build + "." + xxx_Revision
30. reportUrl = 'http://xxx.xxx.xxx.xxx:8080/view/xxx/job/' + JOB_NAME + '/' + BUILD_NUMBER + '/HTML_20Report/'
31. # 连接jenkins
32. server = jenkins.Jenkins(url="http://xxx.xxx.xxx.xxx:8080", username='xxx', password="xxx")
33. testresult = ''
34. packagePath = WORKSPACE + "\\package.ini"
35. overageReportUrl = 'http://xxx.xxx.xxx.xxx/xxx/coverage/' + JOB_NAME + '/Coverage_' + BUILD_NUMBER
36. 
37. def unitTestNotification():
38.     title = 'xxx单测通知'
39.     last_build_number = server.get_job_info(JOB_NAME)['lastCompletedBuild']['number']
40.     build_info = server.get_build_info(JOB_NAME, last_build_number)
41.     # dict字典转json数据
42.     build_info_json = json.dumps(build_info)
43.     # 把json字符串转json对象
44.     build_info_jsonobj = json.loads(build_info_json)
45.     failCount = jsonpath(build_info_jsonobj, '$.actions..failCount')
46.     skipCount = jsonpath(build_info_jsonobj, '$.actions..skipCount')
47.     totalCount = jsonpath(build_info_jsonobj, '$.actions..totalCount')
48.     successCount = totalCount[0] - skipCount[0] - failCount[0]
49.     successRate = round((successCount / totalCount[0]) * 100, 1)
50.     # 判断测试结果
51.     if successRate == 100:
52.         testresult = 'SUCCESS'
53.     else:
54.         testresult = 'FAILURE'
55.     testFail = '#### ' + JOB_NAME + ' - UnitTest # ' + BUILD_NUMBER + ' \n' + \
56.                '##### <font color=#FF0000 size=6 face="黑体">测试结果: ' + testresult + '</font> \n' + \
57.                '##### **版本类型**: ' + '开发版' + '\n' + \
58.                '##### **当前版本**: ' + VERSION + '\n' + \
59.                '##### **用例数**: ' + str(totalCount[0]) + '个 \n' + \
60.                '##### **通过率**: ' + str(successRate) + '% \n' + \
61.                '##### **成功**: ' + str(successCount) + '个 \n' + \
62.                '##### **失败**: ' + str(failCount[0]) + '个 \n' + \
63.                '##### **忽略**: ' + str(skipCount[0]) + '个 \n' + \
64.                '##### **测试报告**:  [查看详情](' + reportUrl + ') \n' + \
65.                '##### **覆盖率报告**:  [查看详情](' + overageReportUrl + ') \n' + \
66.                '##### **关注人**: @18610902487 \n' + \
67.                '> ###### xxx技术团队 \n '
68.     testSuccess = '#### ' + JOB_NAME + ' - UnitTest # ' + BUILD_NUMBER + ' \n' + \
69.                   '##### **测试结果**: ' + testresult + '\n' + \
70.                   '##### **版本类型**: ' + '开发版' + '\n' + \
71.                   '##### **当前版本**: ' + VERSION + '\n' + \
72.                   '##### **用例数**: ' + str(totalCount[0]) + '个 \n' + \
73.                   '##### **通过率**: ' + str(successRate) + '% \n' + \
74.                   '##### **成功**: ' + str(successCount) + '个 \n' + \
75.                   '##### **失败**: ' + str(failCount[0]) + '个 \n' + \
76.                   '##### **忽略**: ' + str(skipCount[0]) + '个 \n' + \
77.                   '##### **测试报告**: [查看详情](' + reportUrl + ') \n' + \
78.                   '##### **覆盖率报告**:  [查看详情](' + overageReportUrl + ') \n' + \
79.                   '> ###### xxx技术团队 \n '
80.     if testresult == 'SUCCESS':
81.         dingText = testSuccess
82.     else:
83.         dingText = testFail
84.     sendding(title, dingText)
85. 
86. def sendding(title, content):
87.     at_mobiles = ['186xxxx2487']
88.     Dingtalk_access_token_v3c = 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx'
89.     # 初始化机器人小丁
90.     xiaoding1 = DingtalkChatbot(Dingtalk_access_token_v3c)
91.     # Markdown消息@指定用户
92.     xiaoding1.send_markdown(title=title, text=content, at_mobiles=at_mobiles)
93. 
94. if __name__ == "__main__":
95.     unitTestNotification()

功能展示

单元测试

测试结果趋势: 持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_10 在线 HtmlReport: 持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_11持续交付之.Net项目单测自动化框架落地(入门篇)_json_12持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_13

覆盖度分析

访问 Tomcat 报告 web 服务: 持续交付之.Net项目单测自动化框架落地(入门篇)_持续交付_14持续交付之.Net项目单测自动化框架落地(入门篇)_json_15

钉钉通知

持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_16

遇到的坑

单元测试中依赖的数据文件或者dll等非引用关系的资源导致测试失败(找不到依赖),开发case的时候需要加标识 DeploymentItem:

持续交付之.Net项目单测自动化框架落地(入门篇)_Jenkins_17

小结

本文带着大家结合 Jenkins 快速入门搭建一款属于自己 .Net 项目单测自动化框架,希望你能有启发。


本文资源:https://github.com/7DGroup/Jenkins-CI/tree/master/jenkins-net-unitautotest