親愛的朋友們:
又有朋友向我提出新的挑戰了!這次我這位好友的需求是,在DevOps流水線中實作自動化測試。聽起來是一件輕松的任務,但問題來了——他們的自動化測試指令碼不完整,需要在程式碼釋出前,透過流水線捕捉Git倉庫的送出差異,智慧定位到具體的Controller層介面,並判斷這個介面可能的影響點從而縮減範圍。
別急,跟著博主一起來解鎖這次的技術之旅!
首先,我們需要一個能夠操作Git程式碼的利器。讓我們招來被廣大開發者稱贊的 LibGit2Sharp 庫。這是個神器,不僅可以Clone程式碼,還能更新程式碼。
拿到Git程式碼的diff檔之後,下一步是提取被修改的行數和函式。這時候,馬上引入強大的 CodeAnalysis 系列元件進入戰場,它能解析計畫程式碼,精確定位方法在 Controller 層的位置。
接下來,看看我們的神兵利器庫:
<PackageReferenceInclude="Microsoft.Build.Locator"Version="1.6.10" />
<PackageReferenceInclude="Microsoft.CodeAnalysis"Version="4.9.0-3.final" />
<PackageReferenceInclude="Microsoft.CodeAnalysis.CSharp"Version="4.9.0-3.final" />
<PackageReferenceInclude="Microsoft.CodeAnalysis.Workspaces.Common"Version="4.9.0-3.final" />
<PackageReferenceInclude="Microsoft.CodeAnalysis.Workspaces.MSBuild"Version="4.9.0-3.final" />
<PackageReferenceInclude="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 callto 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;
namespaceDemo
{
publicstatic classSolutionAnalyzer
{
publicstaticasync 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 != null&&( 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進行自動化測試了。
希望這篇帶有實操和技術解析的文章,能幫助你在自動化測試道路上又推進一大步。後續有任何問題,歡迎交流探討,我們一起進步!