有了自定义单元测试注解后,我们将这个单元测试注解,打到了单元测试方法上:
单元测试方法有了更多的业务信息之后,我们就可以基于Roslyn实现单元测试代码分析了。
四、基于Roslyn实现单元测试代码分析
现有的业务代码到底有多少单元测试,是否全部完成了UnitTest单元测试注解改造,这个统计工作很重要。
这里就用到了Roslyn代码分析技术,大家可以参考第一篇中对Roslyn的详细介绍:.NET Core技术研究-通过Roslyn全面提升代码质量
基于Roslyn实现单元测试代码分析,并将分析后的结果上报到研发效能平台,这样就实现了单元测试数据集中管理,方便后续分析和改进。
通过Roslyn实现单元测试方法的分析过程主要有:
① 创建一个编译工作区MSBuildWorkspace.Create()
② 打开解决方案文件OpenSolutionAsync(slnPath);
③ 遍历Project中的Document
④ 拿到代码语法树、找到所有的方法
⑤ 判断方法是否有UnitTest注解,如果有,将单元测试注解信息统计并上报
看一下实际的代码:
public async Task<List<CodeCheckResult>> CheckSln(string slnPath) { var results = new List<CodeCheckResult>(); try { var slnFile = new FileInfo(slnPath); var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(slnPath); if (solution.Projects != null && solution.Projects.Count() > 0) { foreach (var project in solution.Projects.ToList()) { var documents = project.Documents.Where(x => x.Name.Contains(".cs")); foreach (var document in documents) { var tree = await document.GetSyntaxTreeAsync(); var root = tree.GetCompilationUnitRoot(); if (root.Members == null || root.Members.Count == 0) continue; //member var classDeclartions = root.DescendantNodes().Where(i => i is ClassDeclarationSyntax); foreach (var classDeclare in classDeclartions) { var programDeclaration = classDeclare as ClassDeclarationSyntax; if (programDeclaration == null) continue; foreach (var method in programDeclaration.Members) { if (method.GetType() != typeof(MethodDeclarationSyntax)) continue; //方法 Method var methodDeclaration = (MethodDeclarationSyntax)method; var testAnnotations = methodDeclaration.AttributeLists.Where(i => i.Attributes.FirstOrDefault(a => a.Name.GetText().ToString() == "TestMethod") != null); var teldUnitTestAnnotation = methodDeclaration.AttributeLists.FirstOrDefault(i => i.Attributes.FirstOrDefault(a => a.Name.GetText().ToString() == "UnitTest") != null); if (testAnnotations.Count() > 0) { var result = new UnitTestCodeCheckResult() { Sln = slnFile.Name, ProjectName = project.Name, ClassName = programDeclaration.Identifier.Text, MethodName = methodDeclaration.Identifier.Text, }; if (methodDeclaration.Body.GetText().Lines.Count <= 3) { result.IsEmptyMethod = true; } var methodBody = methodDeclaration.Body.GetText().ToString(); methodBody = methodBody.Replace("{", ""); methodBody = methodBody.Replace("}", ""); methodBody = methodBody.Replace(" ", ""); methodBody = methodBody.Replace("\r\n", ""); if (methodBody.Length == 0) { result.IsEmptyMethod = true; } if (teldUnitTestAnnotation != null) { result.IsTeldUnitTest = true; var args = teldUnitTestAnnotation.Attributes.FirstOrDefault().ArgumentList.Arguments; result.UnitTestCase = args[0].GetText().ToString(); result.SeqNo = args[1].GetText().ToString(); result.UnitTestName = args[2].GetText().ToString(); result.UserName = args[3].GetText().ToString(); result.ServiceType = args[4].GetText().ToString().Replace(" ", ""); if (args.Count >= 7) { result.ServiceID = args[5].GetText().ToString(); result.UnitTestType = args[6].GetText().ToString(); } } results.Add(result); } } } } } } return results; } catch (Exception e) { Console.WriteLine(e.ToString()); return results; } }