亲爱的朋友们:
又有朋友向我提出新的挑战了!这次我这位好友的需求是,在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的结构是怎么样的。
diff --git a/文件路径 b/文件路径
:这行指出了差异以及正在比较的 'a'(旧版本)和 'b'(新版本)的文件路径。例如:diff --git a/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs b/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs
这告诉我们文件
Example08_RetryHandler.cs
在旧版本(a)和新版本(b)之间进行了比较。index df66d963..f950a11a 100644
:这一行显示文件变更前后的index
以及文件模式。df66d963
是变更前文件的 SHA-1 校验和,f950a11a
是变更后的校验和。100644
表示文件模式;对于大多数文件,这意味着它是非可执行文件。--- a/文件路径
和+++ b/文件路径
:这些行标志着在旧文件和新文件版本中变更的开始。
--- a/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs
+++ b/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs
三个减号表示旧版本文件路径,而三个加号表示新版本文件路径。
@@ -35,9 +35,9 @@ public class Example08_RetryHandler : BaseTest
:这行被称为块头部。它告诉我们文件中的变更发生在哪里。-35,9
指的是旧文件(第 35 行到第 43 行),+35,9
指的是新文件。此行其余部分是变更的上下文。修改的行前面有一个减号
-
标记移除的内容和一个加号+
标记添加的内容。
未更改的行也可能显示为变更周围的上下文,没有前导的
+
或-
。
针对您的特定 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进行自动化测试了。
希望这篇带有实操和技术解析的文章,能帮助你在自动化测试道路上又推进一大步。后续有任何问题,欢迎交流探讨,我们一起进步!