我运行CruiseControl.NET并尝试构建一个XSL样式表,从XML构建日志中提取有关已损坏单元测试的某些信息。对于每个损坏的单元测试,我想在XSL中获取单元测试的名称,单元测试所在的类名以及失败消息。目前,我可以获取单元测试名称和失败消息,但是我无法获得类名。我认为这是因为单元测试的类名只包含在XML构建日志的不同区域,而我是XSL的菜鸟。这是我的XSL样式表的简化示例:
<!-- Unit tests -->
<xsl:template match="/">
<table border="1" width="100%">
<tr>
<th align="left">Class</th>
<th align="left">Method</th>
<th align="left">Message</th>
</tr>
<xsl:apply-templates select="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='Results']/*[local-name()='UnitTestResult']"/>
</table>
</xsl:template>
<!-- Failed uint test -->
<xsl:template match="*[local-name()='UnitTestResult'][@outcome='Failed']">
<tr>
<td>
<xsl:apply-templates select="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='TestDefinitions']/*[local-name()='UnitTest'][@id=@testId]"/>
</td>
<td>
<xsl:value-of select="@testName"/>
</td>
<td>
<xsl:value-of select="*[local-name()='Output']/*[local-name()='ErrorInfo']/*[local-name()='Message']"/>
</td>
</tr>
</xsl:template>
<!-- Failed unit test class name -->
<xsl:template match="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='TestDefinitions']/*[local-name()='UnitTest']">
<xsl:value-of select="@className"/>
</xsl:template>
以下是一个简化的示例XML构建日志:
<cruisecontrol>
<build>
<TestRun>
<TestDefinitions>
<UnitTest name="MyUnitTest" storage="Test.dll" id="b17e5b2a-47d0-5f78-1750-07c8ac14518c">
<TestMethod className="UnitTests.cs" name="MyUnitTest" />
</UnitTest>
...
</TestDefinitions>
<Results>
<UnitTestResult testId="b17e5b2a-47d0-5f78-1750-07c8ac14518c" testName="MyUnitTest" outcome="Failed">
<Output>
<ErrorInfo>
<Message>Assert.AreEqual failed</Message>
</ErrorInfo>
</Output>
</UnitTestResult>
...
</Results>
</TestRun>
</build>
</cruisecontrol>
这是当前的输出:
单元测试类名称:
单元测试方法名称:MyUnitTest
失败消息:Assert.AreEqual失败
这是所需的输出:
单元测试类名称:UnitTests.cs
单元测试方法名称:MyUnitTest
失败消息:Assert.AreEqual失败
我的问题是类名始终为空。我试图使用UnitTestResult节点的testId属性来引用文档中其他位置的正确UnitTest节点。我需要什么XSL或Xpath魔法来实现我的目标?
答案 0 :(得分:2)
乍一看,这似乎是你的问题:
<xsl:apply-templates select="/cruisecontrol/build/*[local-name()='TestRun']/*[local-name()='TestDefinitions']/*[local-name()='UnitTest'][@id=@testId]"/>
具体来说,[@id=@testId]
部分。您正在尝试根据UnitTest
的{{1}}属性中的id
属性查找testid
。问题出在这种情况下UnitTestResult
意味着“查找id和testId属性匹配的UnitTest元素。”
你真正想要的是使用current()
函数,它允许你在过滤器中有多个上下文,如下所示:
[@id=@testId]
此外,就像Lucero所评论的那样,您需要删除[@id=current()/@testId]
来电,因为它会让一切变得更简单:
local-name()
作为对查找使用长XPath表达式的替代方法,您可以使用keys代替。它们允许您定义一个键,以后可以用于更短的查找。
定义一个这样的键:
<xsl:apply-templates
select="/cruisecontrol/build/TestRun/TestDefinitions/UnitTest[@id=current()/@testId]"
/>
然后像这样使用它:
<xsl:key
name="tests"
match="/cruisecontrol/build/TestRun/TestDefinitions/UnitTest"
use="@id"/>
这使您不必使用current()函数。无论哪种方式,它都会起作用。
使用<xsl:apply-templates select="key('tests',@testId)"/>
函数可能表示您遇到了命名空间问题。如果local-name()
和build
元素位于不同的命名空间中,则无法使用TestRun
简单地查询它们。这将仅查询默认命名空间中的元素。如果/cruisecontrol/build/TestRun/
在另一个名称空间中,则需要在XSLT文件中定义该名称空间并在表达式中使用其前缀,如下所示:TestRun
其中/cruisecontrol/build/ns:TestRun
是名称空间前缀题。 ns
做的是忽略元素名称的命名空间部分,绕过正确定义命名空间。当您不知道将在源文档中使用哪些名称空间时,它也很有用。