CSharp 后端服务器怎样做到:一边发哀求一边看代码覆盖率

[复制链接]
发表于 2026-6-12 23:11:26 | 显示全部楼层 |阅读模式
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!

  • cnblogs博客
  • zhihu
  • Github

配景

迩来在测试一个服务器的时间,碰到一个困难:我怎样才气构造出多种哀求,以便尽大概的覆盖到全部分支?
写单元测试固然是个办法,但是服务器依赖 mysql 和 redis 等组件,我必要在代码中 mock 这些依赖的组件,单元测试才气独立运行。
有没有办法做到:我发一条哀求,就能立刻看到代码覆盖率的厘革?
下面请看我的演出——
Cooking

all-in-one 调试镜像

我起首制作了一个 DotNet 的 all-in-one 镜像:  see, https://github.com/ahfuzhang/CSharpDbgContainer
镜像紧张是安装了:

  • DotNet SDK (10/8/6)
  • dotnet tool install dotnet-coverage
  • dotnet tool install dotnet-reportgenerator-globaltool
假如不想本身 build 镜像,可以使用我上传的镜像:
  1. docker pull docker.io/ahfuzhang/csharp-dbg-all-in-one:dotnet10
复制代码
see: https://hub.docker.com/repository/docker/ahfuzhang/csharp-dbg-all-in-one/general
焦点代码

代码的原理很简朴:

  • dotnet-coverage collect --session-id ${sessionID} cmd: 通过 dotnet-coverage collect 来启动步伐,而且创建一个 session id
  • dotnet-coverage snapshot --output ${coverageFile} ${sessionID}: 运行期通过 session id 收罗 coverage 信息到一个文件
  • dotnet-coverage merge {coverageFile} --output {coberturaFile} --output-format cobertura:  把 coverage 文件转换为 xml 格式
  • reportgenerator -reports:{coberturaFile} -targetdir:{htmlDir} -reporttypes:Html: 把 xml 文件渲染为 html
  1.    public static async Task CodeCoverageCallbackAsync(HttpContext ctx)
  2.     {
  3.         var sessionId = ctx.Request.Query["session-id"].FirstOrDefault();
  4.         if (string.IsNullOrEmpty(sessionId))
  5.         {
  6.             ctx.Response.StatusCode = 400;
  7.             await ctx.Response.WriteAsync(HtmlError("Missing query parameter: session-id"));
  8.             return;
  9.         }
  10.         var uuid = Guid.NewGuid().ToString("N");
  11.         var coverageFile = $"/tmp/{uuid}.coverage";
  12.         var coberturaFile = $"/tmp/{uuid}.cobertura.xml";
  13.         var htmlDir = $"/tmp/html_{uuid}/";
  14.         // Step 1: snapshot
  15.         var (snapshotOk, snapshotErr) = await RunCommandAsync(
  16.             "dotnet-coverage",
  17.             $"snapshot --output {coverageFile} {sessionId}");
  18.         if (!snapshotOk)
  19.         {
  20.             ctx.Response.StatusCode = 500;
  21.             ctx.Response.ContentType = "text/html; charset=utf-8";
  22.             await ctx.Response.WriteAsync(HtmlError($"dotnet-coverage snapshot failed:\n{snapshotErr}"));
  23.             return;
  24.         }
  25.         // Step 2: convert to cobertura xml
  26.         var (mergeOk, mergeErr) = await RunCommandAsync(
  27.             "dotnet-coverage",
  28.             $"merge {coverageFile} --output {coberturaFile} --output-format cobertura");
  29.         if (!mergeOk)
  30.         {
  31.             ctx.Response.StatusCode = 500;
  32.             ctx.Response.ContentType = "text/html; charset=utf-8";
  33.             await ctx.Response.WriteAsync(HtmlError($"dotnet-coverage merge failed:\n{mergeErr}"));
  34.             return;
  35.         }
  36.         // Step 3: generate html report
  37.         var (reportOk, reportErr) = await RunCommandAsync(
  38.             "reportgenerator",
  39.             $"-reports:{coberturaFile} -targetdir:{htmlDir} -reporttypes:Html");
  40.         if (!reportOk)
  41.         {
  42.             ctx.Response.StatusCode = 500;
  43.             ctx.Response.ContentType = "text/html; charset=utf-8";
  44.             await ctx.Response.WriteAsync(HtmlError($"reportgenerator failed:\n{reportErr}"));
  45.             return;
  46.         }
  47.         ctx.Response.Redirect($"/code_coverage_report/{uuid}/");
  48.     }
复制代码
具体代码请看:https://github.com/ahfuzhang/QiWa.DemoServer/blob/main/src/CodeCoverage/CodeCoverage.cs
编译 debug 版本的 dll
  1. dotnet publish $(PRJ).csproj \
  2.           -r linux-x64 \
  3.           -p:DefineConstants=UNIX -p:AllowUnsafeBlocks=true \
  4.           -p:StripSymbols=false \
  5.           -p:InvariantGlobalization=true \
  6.           -p:EventSourceSupport=true \
  7.           -p:EmbedAllSources=true \
  8.           -p:DebugType=portable \
  9.           -p:DebugSymbols=true \
  10.           -p:Optimize=false \
  11.           -p:CopyOutputSymbolsToPublishDirectory=true \
  12.           -p:TieredCompilation=false \
  13.           --self-contained false \
  14.           -c Debug -o $(BUILD_DIR)
复制代码
特别要注意,不要加这些选项:

  • PublishAot=true : 触发 AOT 原生编译的焦点标记
  • StaticLinkedRuntime=true / StaticExecutable=true — 静态链接原生可实行文件
  • PositionIndependentExecutable=true — 仅对原生二进制故意义
  • --self-contained true: 输生产物将为托管 DLL,依赖体系安装的 .NET 运行时。
为了保障工具集的版本划一性,也可以使用 docker 中的下令来编译:
  1. docker run --rm \
  2.                 -v ./:/app \
  3.                 -w /app \
  4.                 --user "$(id -u):$(id -g)" \
  5.                 ahfuzhang/csharp-dbg-all-in-one:dotnet10 \
  6.                 dotnet publish $(PRJ).csproj \
  7.                         -r linux-x64 \
  8.                         -p:DefineConstants=UNIX -p:AllowUnsafeBlocks=true \
  9.                         -p:StripSymbols=false \
  10.                         -p:InvariantGlobalization=true \
  11.                         -p:EventSourceSupport=true \
  12.                         -p:EmbedAllSources=true \
  13.                         -p:DebugType=portable \
  14.                         -p:DebugSymbols=true \
  15.                         -p:Optimize=false \
  16.                         -p:CopyOutputSymbolsToPublishDirectory=true \
  17.                         -p:TieredCompilation=false \
  18.                         --self-contained false \
  19.                         -c Debug -o $(BUILD_DIR)
复制代码
在 all-in-one 镜像中启动服务器
  1. docker run --rm \
  2.                 -v ./:/app \
  3.                 -w /app \
  4.                 --user "$(id -u):$(id -g)" \
  5.                 --network host \
  6.                 ahfuzhang/csharp-dbg-all-in-one:dotnet10 \
  7.                 dotnet-coverage collect \
  8.                         --session-id my-server-cov \
  9.                         --output /tmp/demo_server.coverage \
  10.                         dotnet $(BUILD_DIR)/QiWa.DemoServer.dll -- \
  11.                                 -log.level=debug
复制代码
注意:

  • 要使用 dotnet-coverage collect 来启动
  • --session-id my-server-cov 这个参数指定了 session id 的名字叫做 my-server-cov,要记着这个名字
  • dotnet xx.dll 保障了步伐使用 dotnet CLR 。假如静态编译的话,大概就无法收罗了
  • -- 这个反面是服务器的下令行参数
通过欣赏器查察代码覆盖环境

打开欣赏器访问:
  1. http://127.0.0.1:8091/code_coverage?session-id=my-server-cov
复制代码

  • 注意:要带上 my-server-cov 这个名字
会触发后端运行下令,并天生 html 陈诉:

每次构造哀求后,我都用欣赏器访问一次 /code_coverage,然后可以瞥见代码的覆盖率在提拔。
总结

在线及时查察代码覆盖率,可以或许很好的办理老项目难以写可 mock 的单元测试的困难。
Have Fun. 😃

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表