作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!
背景
为了更方便地在云环境中测试和调试 CSharp 的后端服务,我制作了 CSharp Debug Container All-in-one。
请看:https://github.com/ahfuzhang/CSharpDbgContainer
原因是多年积累的祖传代码耦合了数据库和 grpc,很难方便的拆出来做单元测试。
因此就需要把服务先启动起来,然后在线测试。
我做了如下事情来支持在线测试:
- 制作好了
CSharp Debug Container All-in-one镜像- 镜像内提供 DebugAdmin 管理进程
- DebugAdmin 启动了 dotnet-coverage; 然后 dotnet-coverage 又启动了目标进程
- 把服务器依赖的其他库,copy 到同一个代码仓库,然后把 csproj 文件中的引用 nuget 仓库,改为引用某个源码目录
- 这样:相当于绝大多数的源码信息都能够被编译到符号文件中
- 把整个服务器及其源码目录打包到镜像,使用
CSharp Debug Container All-in-one作为基础镜像 - 云环境中,修改 deployment 的启动参数,通过 DebugAdmin 来启动好支持在线代码覆盖率的运行环境
请参考前一篇文章,介绍了这种 在线代码覆盖率 的方法:
上面看代码覆盖率的方法在实际部署中有如下困难:
- 所有想看到覆盖情况的代码,都要 copy 到同一目录下;且入口项目的引用信息还得修改。
- 发布二进制的时候,必须把所有源代码 copy 到镜像之中
本文档重点介绍如何解决上面两个问题。
核心思路
- 每个仓库都把源码编译到 pdb 文件之中
- 把 pdb 上传到 NuGet 仓库中
- 入口项目在编译时,把所有依赖的项目中的 pdb 文件拷贝到产物中
- 打包镜像的时候,打包二进制、dll 及其 pdb 文件
- 运行期间采集代码覆盖率后,从 pdb 文件中还原出源码;然后根据覆盖率文件和源码,生成详细到行的覆盖率报表
Cooking,开搞
1. 打包时编译和上传 pdb
- 只需要在命令行中加上选项就行了
VER=0.2.4
# 注意:分号 (;) 要写成 %3b
pack:
dotnet pack -c Release -p:PackageVersion=$(VER) \
--no-restore \
-p:DebugType=portable \
-p:DebugSymbols=true \
-p:IncludeSymbols=false \
-p:EmbedAllSources=true \
-p:AllowedOutputExtensionsInPackageBuildOutputFolder=".dll%3b.pdb"
# make push KEY=$(cat app_key.txt)
push:
dotnet nuget push bin/Release/QiWa.Common.$(VER).nupkg --skip-duplicate \
--api-key $(KEY) \
--source https://api.nuget.org/v3/index.json
可以参考我开源项目中的文件:https://github.com/ahfuzhang/QiWa.Common/blob/main/Makefile
关键参数 -p:EmbedAllSources=true, 把所有源码编译到 pdb 中
需要特别注意: 分号对于 MS Build 而言是个特殊字符,因此
-p:AllowedOutputExtensionsInPackageBuildOutputFolder=".dll%3b.pdb"这个位置中的分号要写成 %3b
- 也可以在 .csproj 文件中增加选项:
<PropertyGroup>
<!-- 默认 pack 只收 .dll/.xml 等,pdb 要显式加进来 -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
方法二
也可以单独把 pdb 文件作为 snupkg 独立上传,这样做的好处是不影响 dotnet restore 时候下载的 NuGet 库。
具体方法如下:
VER=0.2.4
# 注意:分号 (;) 要写成 %3b
pack:
dotnet pack -c Release -p:PackageVersion=$(VER) \
--no-restore \
-p:DebugType=portable \
-p:DebugSymbols=true \
-p:IncludeSymbols=false \
-p:EmbedAllSources=true \
-p:SymbolPackageFormat=snupkg # 打两个包:.nupkg 和 .snupkg
push:
dotnet nuget push bin/Release/QiWa.Common.$(VER).nupkg --skip-duplicate \
--api-key $(KEY) \
--source https://api.nuget.org/v3/index.json
dotnet nuget push bin/Release/QiWa.Common.$(VER).snupkg --skip-duplicate \
--api-key $(KEY) \
--source https://api.nuget.org/v3/index.json # 单独上传 snupkg 包
2. 项目中引用库时,要求带上 pdb 文件
- 在 .csproj 文件中配置
<PropertyGroup>
<CopyDebugSymbolFilesFromPackages>true</CopyDebugSymbolFilesFromPackages>
</PropertyGroup>
- 也可以在命令行提供:
dotnet publish \
-c Release \
-p:CopyDebugSymbolFilesFromPackages=true
方法二
如果是通过 snupkg 上传 pdb,那么依赖的 dll 是不包含 pdb 文件的,需要额外的手动下载、解压、并拷贝到产物中:
wget -O QiWa.Common.1.2.3.snupkg \
"https://www.nuget.org/api/v2/symbolpackage/QiWa.Common/1.2.3"
unzip -l QiWa.Common.1.2.3.snupkg # .snupkg 本质上是 ZIP
3. 打包镜像
只需要把包含 pdb 的产物打包到镜像中就可以了。例如目录为 /wwwroot/
基础镜像为: docker.io/ahfuzhang/csharp-dbg-all-in-one:dotnet10
4. 启动参数
在云环境中启动服务器进程时,采用如下启动参数:
/usr/bin/DebugAdmin \
-admin.port=8081 \
-log.stdout.output \
-with.coverage \
-coverage.xml.settings=/src/build/code.coverage.settings.xml \
-coverage.exclude.re="Dapper" \
-coverage.exclude.re="Pipelines.Sockets.Unofficial" \
-coverage.exclude.re="StackExchange.Redis" \
-coverage.exclude.re="MySqlConnector" \
-coverage.exclude.re="Microsoft.IO.RecyclableMemoryStream" \
-coverage.exclude.re=".*Grpc\\.Protobuf" \
-coverage.source.from.pdb \
-- \
PG.Game.CandyBonanza.dll
- 实现原理上:先启动管理程序,管理程序启动 dotnet-coverage, 然后 coverage 再启动服务器进程 —— 最终实现实时采集代码覆盖率。

各个参数的功能说明如下:
-admin.port=8081: 指定管理端口,用浏览器进入管理端口,会看到如下界面:

-log.stdout.output: 把服务器进程的 stdout 再输出为 DebugAdmin 进程的 stdout-with.coverage: 以采集代码覆盖率的模式启动。此参数一定要加上。coverage.xml.settings=config.xml: 指定一个 dotnet-coverage 命令的配置文件。配置文件中可以声明包含哪些 dll 以及排除哪些 dll。-coverage.exclude.re="$regexp": 配置一个排除某些 class 的正则表达式。-coverage.source.from.pdb: 当未把源码上传到镜像中时,此参数会触发:自动扫描所有 pdb 文件,然后从 pdb 文件中 dump 出 cs 文件,最后生成精确到代码行的 html report.
更多的功能介绍请移步:https://github.com/ahfuzhang/CSharpDbgContainer
总结
- 在线代码覆盖率,对于难以做单元测试的
祖传代码项目特别有用:- 以代码覆盖率为反馈,验证测试用例的有效性
- 便于跟踪复杂的业务流程
- 需要注意:
- release 项目,要额外增加一个脚本删除 pdb 文件后再发布,否则可能有源码泄露的风险
- 可以告诉 AI 做这样一个循环:
- 清空代码覆盖率数据
- 发出请求
- 生成代码覆盖率数据
- 分析代码覆盖率报告
- 生成有效的测试用例
- 回到第一步
- 我的下一篇文章会仔细介绍基于这个 Debug 镜像实现的自动 AI Test 的项目
Have Fun.
😃

浙公网安备 33010602011771号