當前位置: 妍妍網 > 碼農

【.NET DevOps】流水線自動化測試新策略!Git程式碼Diff分析與測試

2024-01-30碼農

親愛的朋友們:

又有朋友向我提出新的挑戰了!這次我這位好友的需求是,在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的結構是怎麽樣的。

  1. diff --git a/檔路徑 b/檔路徑 :這行指出了差異以及正在比較的 'a'(舊版本)和 'b'(新版本)的檔路徑。例如:

  2. diff --git a/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs b/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs

  3. 這告訴我們檔 Example08_RetryHandler.cs 在舊版本(a)和新版本(b)之間進行了比較。

  4. index df66d963..f950a11a 100644 :這一行顯示檔變更前後的 index 以及檔模式。 df66d963 是變更前檔的 SHA-1 校驗和, f950a11a 是變更後的校驗和。 100644 表示檔模式;對於大多數檔,這意味著它是非可執行檔。

  5. --- 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 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 solutionusing (var workspace = MSBuildWorkspace.Create()) {// Load the solutionvar solution = await workspace.OpenSolutionAsync(solutionPath);foreach (var project in solution.Projects) {// Compile the project and get the compilation objectvar compilation = await project.GetCompilationAsync();foreach (var document in project.Documents) {// Parse the document to the syntax treevar syntaxTree = await document.GetSyntaxTreeAsync();var syntaxRoot = await syntaxTree.GetRootAsync();// Find all method declarationsvar methodDeclarations = syntaxRoot.DescendantNodes().OfType<Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax>();foreach (var methodDeclaration in methodDeclarations) {// Check if this is the method we're looking forif (methodDeclaration.Identifier.Text.Equals(methodName)) {var semanticModel = await document.GetSemanticModelAsync();var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration);// Retrieve containing classvar classSymbol = methodSymbol.ContainingType;// Check if containing class is a controllerif ( 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進行自動化測試了。

    希望這篇帶有實操和技術解析的文章,能幫助你在自動化測試道路上又推進一大步。後續有任何問題,歡迎交流探討,我們一起進步!