作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!
配景
迩来在测试一个服务器的时间,碰到一个困难:我怎样才气构造出多种哀求,以便尽大概的覆盖到全部分支?
写单元测试固然是个办法,但是服务器依赖 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 镜像,可以使用我上传的镜像:- 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
- public static async Task CodeCoverageCallbackAsync(HttpContext ctx)
- {
- var sessionId = ctx.Request.Query["session-id"].FirstOrDefault();
- if (string.IsNullOrEmpty(sessionId))
- {
- ctx.Response.StatusCode = 400;
- await ctx.Response.WriteAsync(HtmlError("Missing query parameter: session-id"));
- return;
- }
- var uuid = Guid.NewGuid().ToString("N");
- var coverageFile = $"/tmp/{uuid}.coverage";
- var coberturaFile = $"/tmp/{uuid}.cobertura.xml";
- var htmlDir = $"/tmp/html_{uuid}/";
- // Step 1: snapshot
- var (snapshotOk, snapshotErr) = await RunCommandAsync(
- "dotnet-coverage",
- $"snapshot --output {coverageFile} {sessionId}");
- if (!snapshotOk)
- {
- ctx.Response.StatusCode = 500;
- ctx.Response.ContentType = "text/html; charset=utf-8";
- await ctx.Response.WriteAsync(HtmlError($"dotnet-coverage snapshot failed:\n{snapshotErr}"));
- return;
- }
- // Step 2: convert to cobertura xml
- var (mergeOk, mergeErr) = await RunCommandAsync(
- "dotnet-coverage",
- $"merge {coverageFile} --output {coberturaFile} --output-format cobertura");
- if (!mergeOk)
- {
- ctx.Response.StatusCode = 500;
- ctx.Response.ContentType = "text/html; charset=utf-8";
- await ctx.Response.WriteAsync(HtmlError($"dotnet-coverage merge failed:\n{mergeErr}"));
- return;
- }
- // Step 3: generate html report
- var (reportOk, reportErr) = await RunCommandAsync(
- "reportgenerator",
- $"-reports:{coberturaFile} -targetdir:{htmlDir} -reporttypes:Html");
- if (!reportOk)
- {
- ctx.Response.StatusCode = 500;
- ctx.Response.ContentType = "text/html; charset=utf-8";
- await ctx.Response.WriteAsync(HtmlError($"reportgenerator failed:\n{reportErr}"));
- return;
- }
- ctx.Response.Redirect($"/code_coverage_report/{uuid}/");
- }
复制代码 具体代码请看:https://github.com/ahfuzhang/QiWa.DemoServer/blob/main/src/CodeCoverage/CodeCoverage.cs
编译 debug 版本的 dll
- dotnet publish $(PRJ).csproj \
- -r linux-x64 \
- -p:DefineConstants=UNIX -p:AllowUnsafeBlocks=true \
- -p:StripSymbols=false \
- -p:InvariantGlobalization=true \
- -p:EventSourceSupport=true \
- -p:EmbedAllSources=true \
- -p:DebugType=portable \
- -p:DebugSymbols=true \
- -p:Optimize=false \
- -p:CopyOutputSymbolsToPublishDirectory=true \
- -p:TieredCompilation=false \
- --self-contained false \
- -c Debug -o $(BUILD_DIR)
复制代码 特别要注意,不要加这些选项:
- PublishAot=true : 触发 AOT 原生编译的焦点标记
- StaticLinkedRuntime=true / StaticExecutable=true — 静态链接原生可实行文件
- PositionIndependentExecutable=true — 仅对原生二进制故意义
- --self-contained true: 输生产物将为托管 DLL,依赖体系安装的 .NET 运行时。
为了保障工具集的版本划一性,也可以使用 docker 中的下令来编译:- docker run --rm \
- -v ./:/app \
- -w /app \
- --user "$(id -u):$(id -g)" \
- ahfuzhang/csharp-dbg-all-in-one:dotnet10 \
- dotnet publish $(PRJ).csproj \
- -r linux-x64 \
- -p:DefineConstants=UNIX -p:AllowUnsafeBlocks=true \
- -p:StripSymbols=false \
- -p:InvariantGlobalization=true \
- -p:EventSourceSupport=true \
- -p:EmbedAllSources=true \
- -p:DebugType=portable \
- -p:DebugSymbols=true \
- -p:Optimize=false \
- -p:CopyOutputSymbolsToPublishDirectory=true \
- -p:TieredCompilation=false \
- --self-contained false \
- -c Debug -o $(BUILD_DIR)
复制代码 在 all-in-one 镜像中启动服务器
- docker run --rm \
- -v ./:/app \
- -w /app \
- --user "$(id -u):$(id -g)" \
- --network host \
- ahfuzhang/csharp-dbg-all-in-one:dotnet10 \
- dotnet-coverage collect \
- --session-id my-server-cov \
- --output /tmp/demo_server.coverage \
- dotnet $(BUILD_DIR)/QiWa.DemoServer.dll -- \
- -log.level=debug
复制代码 注意:
- 要使用 dotnet-coverage collect 来启动
- --session-id my-server-cov 这个参数指定了 session id 的名字叫做 my-server-cov,要记着这个名字
- dotnet xx.dll 保障了步伐使用 dotnet CLR 。假如静态编译的话,大概就无法收罗了
- -- 这个反面是服务器的下令行参数
通过欣赏器查察代码覆盖环境
打开欣赏器访问:- http://127.0.0.1:8091/code_coverage?session-id=my-server-cov
复制代码
- 注意:要带上 my-server-cov 这个名字
会触发后端运行下令,并天生 html 陈诉:
每次构造哀求后,我都用欣赏器访问一次 /code_coverage,然后可以瞥见代码的覆盖率在提拔。
总结
在线及时查察代码覆盖率,可以或许很好的办理老项目难以写可 mock 的单元测试的困难。
Have Fun. 😃
|