最近,我不得不重构大量的测试,以便根据报告的结果为它们全部加上特定的特定TestCategory属性。如果测试在报告中列出,则应将其标记为“良好类别”,否则应为“不良类别”。这些类别将用于过滤哪些测试将作为我们的封闭构建的一部分运行。

这里有一些例子。

最佳答案

该过程的第一步是加载解决方案

var wkps = MSBuildWorkspace.Create();
var sln = wkps.OpenSolutionAsync(slnPath).Result;


现在我们有了解决方案参考,我们可以遍历每个Project并获取其SyntaxTrees。然后,我们可以在每个SyntaxTree上调用GetRoot并将其强制转换为CompilationUnitSyntax。从这一点开始,我们搜索所有符合我们的条件的DecsendantNodes,这些条件是定义了TestMethod属性的Method的条件。

这就是所有的样子

foreach (var proj in sln.Projects)
{
    var comp = proj.GetCompilationAsync().Result;
    foreach (var method in root.DescendantNodes().OfType<MethodDeclarationSyntax>().Where(m => HasAttribute(m, TEST_METHOD)))
    {
        //do something with this test method
    }
}


上面有一个名为HasAttribute的帮助程序方法,该方法仅在名称为“ TestMethod”的方法上查找任何Attribute。这是看起来像

bool HasAttribute(MethodDeclarationSyntax method, string attributeName)
{
    return method.AttributeLists
        .Any(al => al.Attributes
            .Any(a => a.Name is IdentifierNameSyntax && (((IdentifierNameSyntax)a.Name).Identifier.Text == attributeName)));
}


现在,我们已经有一种遍历所有TestMethod方法的方法,我们需要为它们分配TestCategory属性。这是上面循环的“执行操作”部分。

这里有两个步骤。首先是编辑SyntaxTree,以便我们添加和/或删除所需的类别。其次是将SyntaxTree写回到源文件中。

我们需要做的第一件事是对照我们的输入列表检查方法的名称。假设我们有一个方法名字典,它可能看起来像这样

var methodName = method.Identifier.ValueText;
var testIsOnList = testDictionary.ContainsKey(methodFullName);


但是,此测试假定在您的整个解决方案中,测试名称在全球范围内是唯一的。不幸的是,在我的情况下,情况并非如此。要解决此问题,我们将输入列表设置为“完全合格的测试名称”,就像它出现在MSTest测试运行程序中一样。这将是:


命名空间

类层次

方法名称




例如My.Long.NameSpace.ParentClass.ChildClass.Method

这是一个小助手方法,将在给定MethodDeclarationSyntax的情况下创建FQTN

string BuildFullTestName(MethodDeclarationSyntax method)
{
    StringBuilder sb = new StringBuilder();
    sb.Append(method.Identifier.ValueText);
    SyntaxNode node = method;
    while(node.Parent is ClassDeclarationSyntax)
    {
        node = node.Parent;
        sb.Insert(0, ".");
        sb.Insert(0, ((ClassDeclarationSyntax)node).Identifier.ValueText);
    }
    if(node.Parent is NamespaceDeclarationSyntax)
    {
        node = node.Parent;
        sb.Insert(0, ".");
        sb.Insert(0, ((NamespaceDeclarationSyntax)node).Name.ToString());
    }
    else
    {
        throw new Exception("method \{method.Identifier.ValueText} has wierd parents.");
    }

    return sb.ToString();
}


因此,我们进行了比较,并希望使用我们的好或不好的TestCategory属性标记一个测试。这是另一个帮助方法,将使用MethodDeclarationSyntax,属性名称(在我们的情况下为TestCateogry)和属性的参数(在我们的情况下为类别的名称)。它将返回新的MethodDeclarationSyntax,其中包括我们的更改。

MethodDeclarationSyntax AddMethodProperty(MethodDeclarationSyntax method, string propertyName, string argumentName)
{
    return method.AddAttributeLists(
            SyntaxFactory.AttributeList(
                SyntaxFactory.SingletonSeparatedList(
                    SyntaxFactory.Attribute(
                        SyntaxFactory.IdentifierName(propertyName),
                        SyntaxFactory.AttributeArgumentList(
                            SyntaxFactory.SingletonSeparatedList(
                                SyntaxFactory.AttributeArgument(
                                    SyntaxFactory.LiteralExpression(
                                        SyntaxKind.StringLiteralExpression,
                                        SyntaxFactory.Token(
                                            default(SyntaxTriviaList),
                                            SyntaxKind.StringLiteralToken,
                                            argumentName,
                                            argumentName,
                                            default(SyntaxTriviaList))
                                        ))))))));
}


因为所有SyntaxNode都是不可变的,所以我们无法就地更新该方法。因此,既然我们有了新的MethodDeclarationSyntax,就需要创建一个新的SyntaxTree,在其中用新方法替换了旧方法。

SyntaxTree newTree = SyntaxFactory.SyntaxTree(
    Formatter.Format(syntaxRoot.ReplaceNode(method, newMethod), wkps))
        .WithFilePath(method.SyntaxTree.FilePath);


注意:.WithFilePath是必需的,以便新的SyntaxTree保留其映射到哪个源文件的上下文。

现在我们可以将新的SyntaxTree写入磁盘。这里的标准东西。

using (StreamWriter file = File.CreateText(method.SyntaxTree.FilePath))
{
    file.Write(newTree.ToString());
    file.Flush();
}


在遍历您的方法时,要记住一个主要陷阱。每次创建新的SyntaxTree时,必须将其根CompilationUnitSyntax传递给循环的未来迭代。此外,仅当要替换的方法实际上来自该SyntaxTree时,我们对以上语法Root.ReplaceNode的调用才有效。换句话说,在新创建的SyntaxTree中找不到您的大嵌套foreach的下一次迭代中的MethodDeclarationSyntax参考。为了解决这个问题,我创建了另一个帮助器方法,该方法将在给定旧方法的情况下在新的SyntaxTree中找到MethodDeclarationSyntax。

MethodDeclarationSyntax GetMethodFromSyntaxRoot(CompilationUnitSyntax root, string nameSpaceName, string className, MethodDeclarationSyntax method)
{
    var result = root.Members.OfType<NamespaceDeclarationSyntax>().Single(ns => ns.Name.ToString() == nameSpaceName)
       .DescendantNodes(d => true).OfType<ClassDeclarationSyntax>().Single(c => c.Identifier.ValueText == className)
           .Members.OfType<MethodDeclarationSyntax>().SingleOrDefault(m => m.Identifier.ValueText == method.Identifier.ValueText && m.ParameterList.ToString() == method.ParameterList.ToString());
}

关于c# - 使用Roslyn重构TestCategory属性,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28160440/

10-10 22:39