玖叶教程网

前端编程开发入门

【.NET DevOps】流水线自动化测试新策略!Git代码Diff分析与测试

亲爱的朋友们:

又有朋友向我提出新的挑战了!这次我这位好友的需求是,在DevOps流水线中实现自动化测试。听起来是一件轻松的任务,但问题来了——他们的自动化测试脚本不完整,需要在代码发布前,通过流水线捕捉Git仓库的提交差异,智能定位到具体的Controller层接口,并判断这个接口可能的影响点从而缩减范围。

别急,跟着博主一起来解锁这次的技术之旅!

首先,我们需要一个能够操作Git代码的利器。让我们招来被广大开发者称赞的LibGit2Sharp库。这是个神器,不仅可以Clone代码,还能更新代码。

拿到Git代码的diff文件之后,下一步是提取被修改的行数和函数。这时候,马上引入强大的CodeAnalysis系列组件进入战场,它能解析项目代码,精确定位方法在Controller层的位置。

接下来,看看我们的神兵利器库:

<PackageReference Include="Microsoft.Build.Locator" Version="1.6.10" /><PackageReference Include="Microsoft.CodeAnalysis" Version="4.9.0-3.final" /><PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.0-3.final" /><PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.9.0-3.final" /><PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.9.0-3.final" /><PackageReference Include="LibGit2Sharp" Version="0.26.2" />

引入这些依赖包后,接着展示我们的核心代码

LibGit2Sharp

一个可以克隆Git仓库的方法:

public (bool, string) GitCloneCode(string repositoryPath, string url, string branch, string username, string password){ try { var co = new CloneOptions();
co.CredentialsProvider = (_url, _user, _cred) => new UsernamePasswordCredentials { Username = username, Password = RSAUtil.Decrypt(password) }; co.BranchName = branch; Repository.Clone(url, repositoryPath, co); flag = true; _logger.LogInformation($"clone代码成功,分支:{branch}地址:{url} --> {repositoryPath}"); return (flag, ""); } catch (Exception ex) { _logger.LogError($"clone代码失败:{ex.Message}--{ex.StackTrace}"); return (flag, ex.Message); }}

这里使用http模式会简单一点,使用账号密码进行拉取。ssh需要配置证书会复杂一些。

紧接着是获取最新代码的操作

public MergeStatus GitPullCode(string username, string password){ var pullOption = new PullOptions() { MergeOptions = new MergeOptions { FastForwardStrategy = FastForwardStrategy.Default }, FetchOptions = new FetchOptions { CredentialsProvider = (_url, _user, _cred) => new UsernamePasswordCredentials { Username = username, Password = RSAUtil.Decrypt(password) } } };
// 拉取最新代码 var result = Commands.Pull(repository, new Signature("git名称", "git邮箱", new DateTimeOffset(DateTime.Now)), pullOption); _logger.LogInformation($"拉取成功:{rep.name}");}

解析git diff

代码拉取完毕后,我们可利用正则匹配或者语义识别技术提取修改函数名。上面更新方法的result 中会包含git diff的差异文本段,我们直接以gitdiff命令来看一看差异内容。

我们可以来看一看git diff的结构是怎么样的。


  1. diff --git a/文件路径 b/文件路径:这行指出了差异以及正在比较的 'a'(旧版本)和 'b'(新版本)的文件路径。例如:

    diff --git a/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs b/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs
  2. 这告诉我们文件 Example08_RetryHandler.cs 在旧版本(a)和新版本(b)之间进行了比较。

  3. index df66d963..f950a11a 100644:这一行显示文件变更前后的 index 以及文件模式。df66d963 是变更前文件的 SHA-1 校验和,f950a11a 是变更后的校验和。100644 表示文件模式;对于大多数文件,这意味着它是非可执行文件。

  4. --- a/文件路径+++ b/文件路径:这些行标志着在旧文件和新文件版本中变更的开始。

--- a/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs+++ b/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs

  1. 三个减号表示旧版本文件路径,而三个加号表示新版本文件路径。

  2. @@ -35,9 +35,9 @@ public class Example08_RetryHandler : BaseTest:这行被称为块头部。它告诉我们文件中的变更发生在哪里。-35,9 指的是旧文件(第 35 行到第 43 行),+35,9 指的是新文件。此行其余部分是变更的上下文。

  3. 修改的行前面有一个减号 - 标记移除的内容和一个加号 + 标记添加的内容。

  • 未更改的行也可能显示为变更周围的上下文,没有前导的 +-

针对您的特定 diff 输出,显示的变更如下:

  • 添加了一行注释:

    + //测试diff
  • 加号(+)表示这一行是新添加到文件的。

  • 移除了一行注释:

- // The call to OpenAI will fail and be retried a few times before eventually failing.
  • 减号(-)表示这一行已经从文件中删除。

其余的以空格开始的行是上下文行(周围未更改的行,帮助您看到变更相对于文件的其他部分的位置)。


然后有了git diff的差异对比后,我们需要通过一些手段去截取出这个diff文件涉及到的函数名。这里可以通过正则表达式去读取这个.cs文件,进行匹配相关修改行的所在Function。


验证效果

最精彩的时刻到了,演示如何通过项目文件和函数名,一举找到潜伏在深处的Controller层:

首先我们需要写一个查找类(包含代码的sln解决方案路径,和要查找的Function名称),然后调用这个查找类:

using Microsoft.Build.Locator;using Microsoft.CodeAnalysis;using Microsoft.CodeAnalysis.MSBuild;
namespace Demo{ public static class SolutionAnalyzer { public static async Task FindControllerForMethodInSolutionAsync(string solutionPath, string methodName) { // Register the MSBuild instance MSBuildLocator.RegisterDefaults();
// Open the solution using (var workspace = MSBuildWorkspace.Create()) { // Load the solution var solution = await workspace.OpenSolutionAsync(solutionPath); foreach (var project in solution.Projects) { // Compile the project and get the compilation object var compilation = await project.GetCompilationAsync(); foreach (var document in project.Documents) { // Parse the document to the syntax tree var syntaxTree = await document.GetSyntaxTreeAsync(); var syntaxRoot = await syntaxTree.GetRootAsync();
// Find all method declarations var methodDeclarations = syntaxRoot.DescendantNodes().OfType<Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax>();
foreach (var methodDeclaration in methodDeclarations) { // Check if this is the method we're looking for if (methodDeclaration.Identifier.Text.Equals(methodName)) { var semanticModel = await document.GetSemanticModelAsync(); var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration);
// Retrieve containing class var classSymbol = methodSymbol.ContainingType;
// Check if containing class is a controller if (classSymbol.BaseType != &&( classSymbol.BaseType.Name.Equals("Controller") || classSymbol.BaseType.Name.Equals("ControllerBase"))) { Console.WriteLine($"查找的方法 '{methodName}' 所在的 controller '{classSymbol.Name}' 项目是 '{project.Name}'."); } } } } } } } }}
SolutionAnalyzer.FindControllerForMethodInSolutionAsync(@"D:\Code\AiAgent.sln", "ImportWebPageAsync").Wait();

让我们来看看效果吧!

非常棒,我们在AiAgent.Api这个项目中查找ImportWebPageAsync方法,我们发现他在KmsController这个Controller中。

这样一来,整个流水线集成自动化测试就像做魔术一样,克隆、拉取,一气呵成,精确解析到位!整合到DevOps中去,只需要轻轻一按,自动化测试便根据Git的提交差异,智能地执行相关的检查,然后我们也可以根据解析出来的接口使用Jmeter进行自动化测试了。

希望这篇带有实操和技术解析的文章,能帮助你在自动化测试道路上又推进一大步。后续有任何问题,欢迎交流探讨,我们一起进步!


发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言