使用XSLT生成Nunit测试报告

最近从mstest转到nunit,原因很简单,mstest如果要脱离IDE单独安装很麻烦,PPE环境下只会安装必要的依赖包,不可能把IDE也装上。相比之下,Nuit就清凉很多了,安装后可以直接用nuit-console.exe来运行测试了。

但问题是测试结果是xml形式的,可读性一般,如下:

 1 <?xml version="1.0" encoding="utf-8" standalone="no"?>
 2 <?xml-stylesheet type="text/xsl" href="nunitresult.xslt"?>
 3  
 4 <!--This file represents the results of running a test suite-->
 5 <test-results name="E:\NUnitTest.dll" total="3" errors="1" failures="0" not-run="0" inconclusive="0" ignored="0" skipped="0" invalid="0" date="2012-07-04" time="10:09:16">
 6   <environment nunit-version="2.6.0.12051" clr-version="2.0.50727.5456" os-version="Microsoft Windows NT 6.1.7601 Service Pack 1" platform="Win32NT" cwd="C:\Program Files\NUnit 2.6\bin" machine-name="Test" user="Tester" user-domain="Test" />
 7   <culture-info current-culture="en-US" current-uiculture="en-US" />
 8   <test-suite type="Assembly" name="E:\NUnitTest.dll" executed="True" result="Failure" success="False" time="0.109" asserts="0">
 9     <results>
10       <test-suite type="Namespace" name="NUnitTest" executed="True" result="Failure" success="False" time="0.103" asserts="0">
11         <results>
12           <test-suite type="TestFixture" name="Class1" executed="True" result="Failure" success="False" time="0.102" asserts="0">
13             <results>
14               <test-case name="NUnitTest.Class1.SampleTest1" description="Description1" executed="True" result="Success" success="False" time="0.061" asserts="0">
15                 <failure>
16                   <message><![CDATA[Error message from assert fail!!]]></message>
17                   <stack-trace><![CDATA[at NUnitTest.Class1.SampleTest1() in E:\NUnitTest\Class1.cs:line 16
18 ]]></stack-trace>
19                 </failure>
20               </test-case>
21               <test-case name="NUnitTest.Class1.SampleTest2" description="Description2" executed="True" result="Error" success="False" time="0.002" asserts="0">
22                 <failure>
23                   <message><![CDATA[System.Exception : Throwing an new exception!!]]></message>
24                   <stack-trace><![CDATA[at NUnitTest.Class1.SampleTest2() in E:\NUnitTest\Class1.cs:line 23
25 ]]></stack-trace>
26                 </failure>
27               </test-case>
28               <test-case name="NUnitTest.Class1.SampleTest3" description="Description3" executed="True" result="Success" success="True" time="0.000" asserts="0" />
29               <test-case name="NUnitTest.Class1.SampleTest4" description="Description4" executed="True" result="Success" success="True" time="0.000" asserts="0" />
30             </results>
31           </test-suite>
32         </results>
33       </test-suite>
34     </results>
35   </test-suite>
36 </test-results>

于是按照解析mstest测试结果的工具trx2html所生成的html报告的样子,自己写了下面的xslt来解析:

  1 <?xml version="1.0" encoding="UTF-8" ?>
  2 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  3     <xsl:template match="/">
  4     <html>
  5         <head>
  6             <title>@@Title@@</title>
  7             <style type='text/css'>
  8                 body, td, th {font:Verdana;vertical-align:top;text-align:left}
  9                 body {margin:0px 0px 0px 15px}
 10                 hr {margin:0px 0px 0px -15px;border:solid 1pt #dcdcdc}
 11                 table {border-collapse:collapse;border:solid 1px #a9a9a9}
 12                 th {background-color:#dcdcdc;border:solid 1px #a9a9a9;text-indent:2pt;font-weight:bolder}
 13                 td {border:solid 1px #a9a9a9}
 14  
 15                 span.ok{background-color:lime;color:black;font-size:40%;color:lime;display:inline-block;}
 16                 span.ko{background-color:red;color:white;font-size:40%;color:red;display:inline-block;}
 17                 span.ignore{background-color:yellow;color:white;font-size:40%;color:yellow;display:inline-block;}
 18  
 19                 p.testKo{
 20                 cursor:hand;
 21                 40px;
 22                 height:40px;
 23                 background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAUCAIAAAAGHlpnAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAPBSURBVDhPY/hPMWDANOHXu1fvTx94v3bmy3kdT6bXP51c9XRi5ctpzS8O7vz57jWmehQj/v398+XKqXfLJ79Z0P5pUce3Re2/5jX9nFn9pb/odUv6w/Koh03Zb88dRzMF2Yh/3y4ce7uw89Pizt+r+v8s7fw1t/7ntIqfEwt/duX8akn5Vhf1MM/3WrrPkz2bkE1BGPH1xvk381q/LO36vbLv18LmX9PKf/bl/ehM/9mc+KM+5nt1+LfywM8FXg8SbM9E2rw8ewxuCtSIv5/ev10+6cOC1t/Lu/+c3g2R/jmz9kdVyM9JxVDuxrmfMhzeJ5pfCNI7kRP++/s3iDjUiK+n9r2eUftrcftvoOMn5P97dh8o9/fJne/VYX8f3QKxH978mGr7IcHiTbThoxDt7c5q97au+fv7N8KIN8snf5ha8WtO/Y9JRT9ak35OLYfY8O/NMxD59dOX8uAP8aZvow2eh2o98lfb7yh/tCTpz9fPCCOeTq7+Mrn0x6Tin51pP2ojvxX7/to0F+7b7ws63scav4vUfx2i+cRP5a6n/FF7qQ2hjj9fv0AYca8950d//o/uzB9Nsd/K/b/muX8tCYAb8SHd4W247psQjZf+yo885W65SJ2wEV3kbvTlznWEEbca0r61pX5vjv9WFfo13+NLluPvM/vhRvw8tuNNkPorP6VnXnL3nSWu2oscNheYY6/98co5hBF3GtLfV4Z/B+ov9P2a6fxz7XSI/u+b5kEYXxZ0vvSUfeQscdNO+Jwl/2YDnoVOel/ug0IaGiMP+2seZ7p/LwDa7/S1Kuzfl09AuR8b574N1vi+cwUoRD9/eBlnds9O+JIF/0lTngWanNsyI/58BimDGvH64PYLMTZvUm0/Jln+unISFIsvHr+NM33tr/Q6XPfPs4cgF505cNWC76Qx1y5djn413ht7d/77+xdhxO9PHy5Wpp4PNnoeafg8TPdZkOazANVnPgrPPGSfuog/chS9ayN4xZz7hBHHXj3W2Socy+P8/4ATBcIIIOvlhVOHwm1PeGnc9lW946V420P2tovkTQexG7aCVyx5z5lxHTPk2KPLMl+VdZql2sNLF+CBjZJTb2/fsMXLcIe93Dkn6YsOEudtRM5ZCZwx4z1mxHlQn22bFutMZdYpBtLnt26E60dxBUT0wZH922PcF5rJrDYU3mrIt8eQZ7sO1xoN9nmq7P1KnHOcDZ8f3YesH4sRoGB78+rKqkWbkoNmOehOsFTvtVSfYqm62Nvi9KS2T08foenHbgSmIvwiACPu1otCSuPAAAAAAElFTkSuQmCC);
 24                 background-repeat:no-repeat
 25                 }                
 26                 p.testOk{
 27                 cursor:hand;
 28                 40px;
 29                 height:40px;
 30                 background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAMTSURBVDhPpdTrL5tRGABw/80+LCKzGHHJqEt19KWoy2sW0a5D0a6hVcWoKu26Tlt1p1Uzd6WoqdVtQ0vEZW6xi2UXsw3r0FKq3ZHKeGUfljgfTvKcnF+ec3nOcbBdoTlcwdou40+/PopHH5M74sk98TQNiTbwIFF5l/AspnRE+O33xqVMCDz+YZSiJNS9kejML7XW7o49heyLRPqWx5t9lNabAEl9VXMdF/05nlx/laFOntjVLNp0mpPOpu2qinWBcInDm83h6JlsPYMxmOwvdG+davzrz/DW/k+mmqI3audt471HLfWbUtEKt3g2m62n502ms8aoJCVM6U8gq+K9i1zefV+z+zPct9QlneMt2CbUx62yjVLhIrtAz8h9TWONpmWOpOLlgUINV7XaTu0hRlRj0ptTEZimSho6UA1alYrNspJlDkjIGqMwhlPp2mRcbUC+imk5sZgth6x+WqwiNLgEhcDUgfv9lrbmnRrRKjdjKDFrNI0xlJKhJUfUBWYr0/cP944sR5U6UVwjLlaB8+a7IDBdS+4yNoITki7zRSM8WAFlDqfg5RhmB/Xw+ABMrdZJIhvuwIoQWB7iK3BDYEofESy4eD57Zkt3bDlmd2ehSz0y2ykms8lqtVZPlUUqMEBG1WHxNUE+fFcEJrZHi1YKOdPMp9PcXbPBZDYWdLNMR0YwqUYvDZej4QYoqi4ovCowtDzAi+uMwLm9dLaOXqBjUDUEsY5nXypotfqyMHlAjAKKkmGBDCnzDxb7YAS3EXhwWU3qjM0Ze5g+kJigjJTNVHw1fC7S5obL0HA9FuSMOJV+kAQF7lmsESCwwWSIKscS26IpfYQkZVxcM47YAoOjjpaDTWJwlWhI6hcsQaGfeLrmOa5triIwCNQLKh/erbiGMFIbHP8cD8ux0TIsKAlcBRoq9Q0WowIEns7Z10pe8C+Xpz3WLKqhEn9QwJAUFVaBxlcHgR57mtDLvcDJJfe6RCP898Owj+7sb7dPN5Fk99zyHd3ZTh6FN0DvxblJries/3h/UZ7X9qXR/wyv9JP8AUelhZbR6oNqAAAAAElFTkSuQmCC);
 31                 background-repeat:no-repeat
 32                 }
 33                 p.testIgnore{
 34                 cursor:hand;
 35                 40px;
 36                 height:40px;
 37                 background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAIAAAAf7rriAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJeSURBVDhPY/hPAWDApfff328EjcWu+de3J49PWD47a3lym/Xlk0txmYJd89s7kz48UvnxLfDKEb4p1ZJ//vzGqh+L5r+/Pzw5ZfLvj+P//57//0tOreY+f2QWsZo/Pel9f8/h3//QRw9k//6TO7aTc26rGlGa//x8+vK83f+/BQsW+SnKa/d1G/z/zzetmvPCifWY+tGd/eH+1E/3ff//r01MtmFg8A3xsv7/X2j/GqbZLToENP/99fHJSZd/v5r+/8/sbjNkYkiY1a3+/z//15fcE8q47t44jqYfxeZPj2e/ueXz/3/v//+uuzbJKfA3Xjyi9P8Py9/v4ptnsM7rcsCp+d/fj8/Oevz6Vvz/f97//2pHDonqyMfdvSL4/wPDrw+ib66z9ZdyPn9yB1k/wubvr5a9ugb0IVCnw/+//FcvcCV46jy/yfb/GcPPR7x/Pois6GFeNTMSi+Z/fz68OOf+/bPX//9u/z9L/H/J+v8z07NznP/fMvy/w/DzCtOv95JPz3NOLOP9/u0DXD/U5q/PV7w6L/fvv+X/TzL/H7D/e8O8eBqLvy/7poXsQJt/nWP4eovn71vRpe2sy2fEomj+8Pb+9e0W3+9I//8s9f8W2/9bzN+fMViZMjIwxBWnJP5/wfDrLMP3c4x/ngg+Pcg0sd4eRfPrp2e3T+d/tIf7xWG2J7uZHu1gen6IcXk3U1WK2/k1Zo/3MN7Zwnh/K+OjXUwv9jGu6xdH0fz719cbh9Jn1LBPLmMDoklgcloVELFOqWCdVAZEIBEgmlrJuqBDE93PBLMuUWmbJFMAIcChwBwUNxQAAAAASUVORK5CYII=);
 38                 background-repeat:no-repeat
 39                 }
 40                 div.barContainer{100%;border:solid 1px black}
 41                 div.trace{font:100% Courier}
 42                 div.border{border:dotted 1px #dcdcdc;padding:2px 2px 2px 2px}
 43                 div.contents{border:dotted 1px #dcdcdc;padding:2px 2px 2px 2px;background-color:#efefef}
 44             </style>
 45         </head>
 46         <body>
 47             <a name='__top' />
 48             <h3>@@Title@@</h3>
 49             <div class='contents'>
 50                 Summary
 51             </div>
 52             <br />
 53             <a name='totals' />
 54             <table id='tMainSummary'  border='0' width='900px'>
 55                 <tr>
 56                     <th>Percent</th>
 57                     <th>Status</th>
 58                     <th>TotalTests</th>
 59                     <th>Errors</th>
 60                     <th>Failures</th>
 61                     <th>NotRun</th>
 62                     <th>Inconclusive</th>
 63                     <th>Ignored</th>
 64                     <th>Skipped</th>
 65                     <th>Invalid</th>
 66                     <th>TimeTaken</th>
 67                 </tr>
 68                 <tr>
 69                     <xsl:apply-templates />
 70                 </tr>
 71             </table>
 72             <br />
 73             <br />
 74             <div class='contents'>
 75                 Detail
 76             </div>
 77             <br />
 78             <table id="tSlowerMethods" border='0' width='900px'>
 79                 <tr>
 80                     <th>TestMethod</th>
 81                     <th colspan='2'>Status</th>
 82                     <th>Duration</th>
 83                 </tr>
 84                 <xsl:for-each select="//test-case">
 85                     <tr>
 86                         <td><xsl:value-of select="@name"/></td>
 87                         <td>
 88                             <xsl:choose>
 89                                 <xsl:when test="@result = 'Success'">
 90                                     <p class='testOk'></p>
 91                                 </xsl:when>
 92                                 <xsl:otherwise>
 93                                     <p class='testKo'></p>
 94                                 </xsl:otherwise>
 95                             </xsl:choose>
 96                         </td>
 97                         <td width='100%'>
 98                             <xsl:value-of select="@description"/><br />
 99                             <xsl:value-of select="failure/message"/><br />
100                             <xsl:value-of select="failure/stack-trace"/>
101                         </td>
102                         <td><xsl:value-of select="@time"/></td>
103                     </tr>
104                 </xsl:for-each>
105             </table>
106         </body>
107     </html>
108     </xsl:template>
109  
110     <xsl:template match="test-results">
111         <td>
112             <xsl:value-of 
113                 select="round(((number(@total)-number(@errors)-number(@failures)-number(@not-run)-number(@inconclusive)-number(@ignored)-number(@skipped)-number(@invalid)) div @total) * 100)"/>%
114         </td>
115         <td width='350px' style='vertical-align:middle;font-size:200%'>
116             <div class="barContainer">
117                 <span class="ok" title="Passed!">
118                     <xsl:attribute name="style">
119                         <xsl:value-of select="round(((number(@total)-number(@errors)-number(@failures)-number(@not-run)-number(@inconclusive)-number(@ignored)-number(@skipped)-number(@invalid)) div @total) * 100)"/>%
120                 </xsl:attribute>p</span>
121                 <span class="ko" title="Failed">
122                     <xsl:attribute name="style">
123                         <xsl:value-of select="100-round(((number(@total)-number(@errors)-number(@failures)-number(@not-run)-number(@inconclusive)-number(@ignored)-number(@skipped)-number(@invalid)) div @total) * 100)"/>%
124                     </xsl:attribute>
125                     f</span>
126             </div>
127         </td>
128         <td><xsl:value-of select="@total"/></td>
129         <td><xsl:value-of select="@errors"/></td>
130         <td><xsl:value-of select="@failures"/></td>
131         <td><xsl:value-of select="@not-run"/></td>
132         <td><xsl:value-of select="@inconclusive"/></td>
133         <td><xsl:value-of select="@ignored"/></td>
134         <td><xsl:value-of select="@skipped"/></td>
135         <td><xsl:value-of select="@invalid"/></td>
136         <td><xsl:value-of select="@date"/>&#160;<xsl:value-of select="@time"/></td>
137     </xsl:template>
138     </xsl:stylesheet>

其中css样式是抄trx2html报告的,并做了一些改动。

这是解析后的样子:

加了个@@Title@@可以在解析代码中替换为想要的标题。

好了,之后就写个工具解析和生成html report文件吧:

using System;
using System.IO;
using System.Xml;
using System.Xml.Xsl;
 
namespace NUnitHtmlTestResultParser
{
    class Program
    {
        static int Main(string[] args)
        {
            if (args.Length != 3)
            {
                Console.WriteLine(HelpInfo());
                return 1;
            }
            if (!File.Exists(args[0]))
            {
                Console.WriteLine("Can not find result file " + args[0]);
                Console.WriteLine(HelpInfo());
                return 2;
            }
            try
            {
                XmlDocument xDoc = new XmlDocument();
                xDoc.Load(args[0]);
                string returnhtml = ConvertXML(xDoc, @"nunitresult.xslt", new XsltArgumentList()).Replace("@@Title@@", args[2]);
                using (TextWriter tw = new StreamWriter(args[1]))
                {
                    tw.Write(returnhtml);
                }
                Console.WriteLine("Done!!");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine(HelpInfo());
                return 1;
            }
            return 0;
        }
 
        private static string HelpInfo()
        {
            return "Usage: NUnitHtmlTestResultParser.exe SOURCE DEST TITLE" + System.Environment.NewLine +
                "SOURCE is nunit test result file" + System.Environment.NewLine +
                "DEST is parsed html test result file" + System.Environment.NewLine +
                "TITLE is test report title" + System.Environment.NewLine;
        }
 
        private static string ConvertXML(XmlDocument InputXMLDocument, string XSLTFilePath
, XsltArgumentList XSLTArgs)
        {
            using (StringWriter sw = new StringWriter())
            {
                XslCompiledTransform xslTrans = new XslCompiledTransform();
                xslTrans.Load(XSLTFilePath);
                xslTrans.Transform(InputXMLDocument.CreateNavigator(), XSLTArgs, sw);
                return sw.ToString();
            }
        }
    }
}

好了,试试这个工具:
$ NUnitHtmlTestResultParser.exe result.xml result.html “Test result for abc”
$ Done!!

打开result.html看看:

由于我的测试结果是要通过邮件发出的,并且都是通过outlook收发邮件的,如果以上面这种html直接发到邮箱里面的话,展现的结果是这个鬼样子:

好吧,继续修改之前的xslt,使其能在outlook中显示正常:

 

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
    <html>
        <head>
            <title>@@Title@@</title>
            <style type='text/css'>
                body, td, th {font:Verdana;vertical-align:top;text-align:left}
                body {margin:0px 0px 0px 15px}
                hr {margin:0px 0px 0px -15px;border:solid 1pt #dcdcdc}
                table {border-collapse:collapse;border:solid 1px #a9a9a9}
                th {background-color:#dcdcdc;border:solid 1px #a9a9a9;text-indent:2pt;font-weight:bolder}
                td {border:solid 1px #a9a9a9}
 
                span.ok{background-color:lime;color:black;font-size:40%;color:lime;display:inline-block;}
                span.ko{background-color:red;color:white;font-size:40%;color:red;display:inline-block;}
                span.ignore{background-color:yellow;color:white;font-size:40%;color:yellow;display:inline-block;}
 
                p.testKo{
                cursor:hand;
                40px;
                height:40px;
                background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAUCAIAAAAGHlpnAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAPBSURBVDhPY/hPMWDANOHXu1fvTx94v3bmy3kdT6bXP51c9XRi5ctpzS8O7vz57jWmehQj/v398+XKqXfLJ79Z0P5pUce3Re2/5jX9nFn9pb/odUv6w/Koh03Zb88dRzMF2Yh/3y4ce7uw89Pizt+r+v8s7fw1t/7ntIqfEwt/duX8akn5Vhf1MM/3WrrPkz2bkE1BGPH1xvk381q/LO36vbLv18LmX9PKf/bl/ehM/9mc+KM+5nt1+LfywM8FXg8SbM9E2rw8ewxuCtSIv5/ev10+6cOC1t/Lu/+c3g2R/jmz9kdVyM9JxVDuxrmfMhzeJ5pfCNI7kRP++/s3iDjUiK+n9r2eUftrcftvoOMn5P97dh8o9/fJne/VYX8f3QKxH978mGr7IcHiTbThoxDt7c5q97au+fv7N8KIN8snf5ha8WtO/Y9JRT9ak35OLYfY8O/NMxD59dOX8uAP8aZvow2eh2o98lfb7yh/tCTpz9fPCCOeTq7+Mrn0x6Tin51pP2ojvxX7/to0F+7b7ws63scav4vUfx2i+cRP5a6n/FF7qQ2hjj9fv0AYca8950d//o/uzB9Nsd/K/b/muX8tCYAb8SHd4W247psQjZf+yo885W65SJ2wEV3kbvTlznWEEbca0r61pX5vjv9WFfo13+NLluPvM/vhRvw8tuNNkPorP6VnXnL3nSWu2oscNheYY6/98co5hBF3GtLfV4Z/B+ov9P2a6fxz7XSI/u+b5kEYXxZ0vvSUfeQscdNO+Jwl/2YDnoVOel/ug0IaGiMP+2seZ7p/LwDa7/S1Kuzfl09AuR8b574N1vi+cwUoRD9/eBlnds9O+JIF/0lTngWanNsyI/58BimDGvH64PYLMTZvUm0/Jln+unISFIsvHr+NM33tr/Q6XPfPs4cgF505cNWC76Qx1y5djn413ht7d/77+xdhxO9PHy5Wpp4PNnoeafg8TPdZkOazANVnPgrPPGSfuog/chS9ayN4xZz7hBHHXj3W2Socy+P8/4ATBcIIIOvlhVOHwm1PeGnc9lW946V420P2tovkTQexG7aCVyx5z5lxHTPk2KPLMl+VdZql2sNLF+CBjZJTb2/fsMXLcIe93Dkn6YsOEudtRM5ZCZwx4z1mxHlQn22bFutMZdYpBtLnt26E60dxBUT0wZH922PcF5rJrDYU3mrIt8eQZ7sO1xoN9nmq7P1KnHOcDZ8f3YesH4sRoGB78+rKqkWbkoNmOehOsFTvtVSfYqm62Nvi9KS2T08foenHbgSmIvwiACPu1otCSuPAAAAAAElFTkSuQmCC);
                background-repeat:no-repeat
                }                
                p.testOk{
                cursor:hand;
                40px;
                height:40px;
                background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAMTSURBVDhPpdTrL5tRGABw/80+LCKzGHHJqEt19KWoy2sW0a5D0a6hVcWoKu26Tlt1p1Uzd6WoqdVtQ0vEZW6xi2UXsw3r0FKq3ZHKeGUfljgfTvKcnF+ec3nOcbBdoTlcwdou40+/PopHH5M74sk98TQNiTbwIFF5l/AspnRE+O33xqVMCDz+YZSiJNS9kejML7XW7o49heyLRPqWx5t9lNabAEl9VXMdF/05nlx/laFOntjVLNp0mpPOpu2qinWBcInDm83h6JlsPYMxmOwvdG+davzrz/DW/k+mmqI3audt471HLfWbUtEKt3g2m62n502ms8aoJCVM6U8gq+K9i1zefV+z+zPct9QlneMt2CbUx62yjVLhIrtAz8h9TWONpmWOpOLlgUINV7XaTu0hRlRj0ptTEZimSho6UA1alYrNspJlDkjIGqMwhlPp2mRcbUC+imk5sZgth6x+WqwiNLgEhcDUgfv9lrbmnRrRKjdjKDFrNI0xlJKhJUfUBWYr0/cP944sR5U6UVwjLlaB8+a7IDBdS+4yNoITki7zRSM8WAFlDqfg5RhmB/Xw+ABMrdZJIhvuwIoQWB7iK3BDYEofESy4eD57Zkt3bDlmd2ehSz0y2ykms8lqtVZPlUUqMEBG1WHxNUE+fFcEJrZHi1YKOdPMp9PcXbPBZDYWdLNMR0YwqUYvDZej4QYoqi4ovCowtDzAi+uMwLm9dLaOXqBjUDUEsY5nXypotfqyMHlAjAKKkmGBDCnzDxb7YAS3EXhwWU3qjM0Ze5g+kJigjJTNVHw1fC7S5obL0HA9FuSMOJV+kAQF7lmsESCwwWSIKscS26IpfYQkZVxcM47YAoOjjpaDTWJwlWhI6hcsQaGfeLrmOa5triIwCNQLKh/erbiGMFIbHP8cD8ux0TIsKAlcBRoq9Q0WowIEns7Z10pe8C+Xpz3WLKqhEn9QwJAUFVaBxlcHgR57mtDLvcDJJfe6RCP898Owj+7sb7dPN5Fk99zyHd3ZTh6FN0DvxblJries/3h/UZ7X9qXR/wyv9JP8AUelhZbR6oNqAAAAAElFTkSuQmCC);
                background-repeat:no-repeat
                }
                p.testIgnore{
                cursor:hand;
                40px;
                height:40px;
                background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAIAAAAf7rriAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJeSURBVDhPY/hPAWDApfff328EjcWu+de3J49PWD47a3lym/Xlk0txmYJd89s7kz48UvnxLfDKEb4p1ZJ//vzGqh+L5r+/Pzw5ZfLvj+P//57//0tOreY+f2QWsZo/Pel9f8/h3//QRw9k//6TO7aTc26rGlGa//x8+vK83f+/BQsW+SnKa/d1G/z/zzetmvPCifWY+tGd/eH+1E/3ff//r01MtmFg8A3xsv7/X2j/GqbZLToENP/99fHJSZd/v5r+/8/sbjNkYkiY1a3+/z//15fcE8q47t44jqYfxeZPj2e/ueXz/3/v//+uuzbJKfA3Xjyi9P8Py9/v4ptnsM7rcsCp+d/fj8/Oevz6Vvz/f97//2pHDonqyMfdvSL4/wPDrw+ib66z9ZdyPn9yB1k/wubvr5a9ugb0IVCnw/+//FcvcCV46jy/yfb/GcPPR7x/Pois6GFeNTMSi+Z/fz68OOf+/bPX//9u/z9L/H/J+v8z07NznP/fMvy/w/DzCtOv95JPz3NOLOP9/u0DXD/U5q/PV7w6L/fvv+X/TzL/H7D/e8O8eBqLvy/7poXsQJt/nWP4eovn71vRpe2sy2fEomj+8Pb+9e0W3+9I//8s9f8W2/9bzN+fMViZMjIwxBWnJP5/wfDrLMP3c4x/ngg+Pcg0sd4eRfPrp2e3T+d/tIf7xWG2J7uZHu1gen6IcXk3U1WK2/k1Zo/3MN7Zwnh/K+OjXUwv9jGu6xdH0fz719cbh9Jn1LBPLmMDoklgcloVELFOqWCdVAZEIBEgmlrJuqBDE93PBLMuUWmbJFMAIcChwBwUNxQAAAAASUVORK5CYII=);
                background-repeat:no-repeat
                }
                div.barContainer{100%;border:solid 1px black}
                div.trace{font:100% Courier}
                div.border{border:dotted 1px #dcdcdc;padding:2px 2px 2px 2px}
                div.contents{border:dotted 1px #dcdcdc;padding:2px 2px 2px 2px;background-color:#efefef}
            </style>
        </head>
        <body>
            <a name='__top' />
            <h3>@@Title@@</h3>
            <div class='contents'>
                Summary
            </div>
            <br />
            <a name='totals' />
            <table id='tMainSummary'  border='0' width="1000">
                <tr>
        <th width="40">Percent</th>
        <th width="150">Status</th>
        <th width="40">TotalTests</th>
        <th width="40">Errors</th>
        <th width="40">Failures</th>
        <th width="40">NotRun</th>
        <th width="40">Inconclusive</th>
        <th width="40">Ignored</th>
        <th width="40">Skipped</th>
        <th width="40">Invalid</th>
        <th width="40">TimeTaken</th>
                </tr>
                <tr>
                    <xsl:apply-templates />
                </tr>
            </table>
            <br />
            <br />
            <div class='contents'>
                Detail
            </div>
            <br />
            <table id="tSlowerMethods" border='0' width="1000">
                <tr>
                    <th width="202">TestMethod</th>
                    <th width="58">Status</th>
                    <th width="661">Message</th>
                    <th width="79">Duration</th>
                </tr>
                <xsl:for-each select="//test-case">
                    <tr>
                        <td><xsl:value-of select="@name"/></td>
                        <td>
                            <xsl:choose>
                                <xsl:when test="@result = 'Success'">
                                    <div style="color: #008000; font-weight: bolder">PASS</div>
                                </xsl:when>
                                <xsl:otherwise>
                                    <div style="color: #FF0000; font-weight: bolder">FAIL</div>
                                </xsl:otherwise>
                            </xsl:choose>
                        </td>
                        <td>
                            <xsl:value-of select="@description"/><br />
                            <xsl:value-of select="failure/message"/><br />
                            <xsl:value-of select="failure/stack-trace"/>
                        </td>
                        <td><xsl:value-of select="@time"/></td>
                    </tr>
                </xsl:for-each>
            </table>
        </body>
    </html>
    </xsl:template>
 
    <xsl:template match="test-results">
        <td>
            <xsl:value-of 
                select="round(((number(@total)-number(@errors)-number(@failures)-number(@not-run)-number(@inconclusive)-number(@ignored)-number(@skipped)-number(@invalid)) div @total) * 100)"/>%
        </td>
        <td width='350px' style='vertical-align:middle;font-size:200%'>
                    <table border="0" width="100%">
                        <tr>
                            <xsl:choose>
                                <xsl:when test="round(((number(@total)-number(@errors)-number(@failures)-number(@not-run)-number(@inconclusive)-number(@ignored)-number(@skipped)-number(@invalid)) div @total) * 100) = 100">
                                    <td bgcolor="Green" style="color: #008000" width="100%">P</td>
                                </xsl:when>
                                <xsl:when test="round(((number(@total)-number(@errors)-number(@failures)-number(@not-run)-number(@inconclusive)-number(@ignored)-number(@skipped)-number(@invalid)) div @total) * 100) = 0">
                                    <td bgcolor="Red" style="color: #FF0000" width="100%">F</td>
                                </xsl:when>
                                <xsl:otherwise>
                                    <td bgcolor="Green" style="color: #008000"><xsl:attribute name="width"><xsl:value-of select="round(((number(@total)-number(@errors)-number(@failures)-number(@not-run)-number(@inconclusive)-number(@ignored)-number(@skipped)-number(@invalid)) div @total) * 100)"/>%</xsl:attribute>P</td>
                                    <td bgcolor="Red" style="color: #FF0000"><xsl:attribute name="width">
                                            <xsl:value-of select="100-round(((number(@total)-number(@errors)-number(@failures)-number(@not-run)-number(@inconclusive)-number(@ignored)-number(@skipped)-number(@invalid)) div @total) * 100)"/>%</xsl:attribute>F</td>
                                </xsl:otherwise>
                            </xsl:choose>
                        </tr>
                    </table>
        </td>
        <td><xsl:value-of select="@total"/></td>
        <td><xsl:value-of select="@errors"/></td>
        <td><xsl:value-of select="@failures"/></td>
        <td><xsl:value-of select="@not-run"/></td>
        <td><xsl:value-of select="@inconclusive"/></td>
        <td><xsl:value-of select="@ignored"/></td>
        <td><xsl:value-of select="@skipped"/></td>
        <td><xsl:value-of select="@invalid"/></td>
        <td><xsl:value-of select="@date"/> <xsl:value-of select="@time"/></td>
    </xsl:template>
 
    </xsl:stylesheet>

好了,再通过outlook看看:

搞定。

 

原文地址:https://www.cnblogs.com/liupengblog/p/2678477.html