I have been lately working a bit with with the Visual Studio unit testing framework and mstest.exe. One thing about it that confused me is the largely complex trx file format. Looking at the results file of a test run without using Visual Studio or without publishing the results to TFS can be pretty cumbersome. I came across some XSLT files online which can help visualize the test results to a friendly form - which is one way to do it.
I thought it would be better if I could programmatically parse the trx files and generate reports in any form I want. It was fairly easy, since the trx file schema is available with Visual Studio installation. It is named vstst.xsd and can be found under your Visual Studio installation directory. You can easily run the xsd.exe tool on this schema and generate the C# classes to parse the trx files.
Given below is some code for a console application that utilizes the classes generated by the xsd.exe tool to parse all trx files in a given folder and generate a pretty looking excel report.
Be sure to add a reference to Microsoft.Office.Interop.Excel in your project for the below code to work.
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: string fileName;
6:
7: int aborted = 0,
8: passed = 0,
9: failed = 0,
10: notexecuted = 0;
11:
12: if (args.Length < 1)
13: {
14: return;
15: }
16:
17: try
18: {
19: // Construct DirectoryInfo for the folder path passed in as an argument
20: DirectoryInfo di = new DirectoryInfo(args[0]);
21:
22: Excel.Application oXL = null;
23: Excel.Workbook oWB;
24: Excel.Worksheet oSheet;
25:
26: // Get a refrence to Excel
27: oXL = new Excel.Application();
28:
29: // Create a workbook and add sheet
30: oWB = (Excel.Workbook)oXL.Workbooks.Add(Excel.XlWBATemplate.xlWBATWorksheet);
31: oSheet = (Excel.Worksheet)oWB.ActiveSheet;
32: oSheet.Name = "trx";
33: oXL.Visible = true;
34: oXL.UserControl = true;
35:
36: // Write the column names to the work sheet
37: oSheet.Cells[1, 1] = "Processed File Name";
38: oSheet.Cells[1, 2] = "Test ID";
39: oSheet.Cells[1, 3] = "Test Name";
40: oSheet.Cells[1, 4] = "Test Outcome";
41:
42: int row = 2;
43:
44: // For each .trx file in the given folder process it
45: foreach (FileInfo file in di.GetFiles("*.trx"))
46: {
47:
48: fileName = file.Name;
49:
50: // Deserialize TestRunType object from the trx file
51: StreamReader fileStreamReader = new StreamReader(file.FullName);
52:
53: XmlSerializer xmlSer = new XmlSerializer(typeof(TestRunType));
54:
55: TestRunType testRunType = (TestRunType)xmlSer.Deserialize(fileStreamReader);
56:
57: // Navigate to UnitTestResultType object and update the sheet with test result information
58: foreach (object itob1 in testRunType.Items)
59: {
60: ResultsType resultsType = itob1 as ResultsType;
61:
62: if (resultsType != null)
63: {
64: foreach (object itob2 in resultsType.Items)
65: {
66: UnitTestResultType unitTestResultType = itob2 as UnitTestResultType;
67:
68: if (unitTestResultType != null)
69: {
70: oSheet.Cells[row, 1] = fileName;
71: oSheet.Cells[row, 2] = unitTestResultType.testId;
72: oSheet.Cells[row, 3] = unitTestResultType.testName;
73: oSheet.Cells[row, 4] = unitTestResultType.outcome;
74:
75: if (0 == unitTestResultType.outcome.CompareTo("Aborted"))
76: {
77: oSheet.Rows.get_Range("A" + row.ToString(), "D" + row.ToString()).Interior.Color = System.Drawing.ColorTranslator.ToWin32(Color.Yellow);
78: aborted++;
79: }
80: else if (0 == unitTestResultType.outcome.CompareTo("Passed"))
81: {
82: oSheet.Rows.get_Range("A" + row.ToString(), "D" + row.ToString()).Interior.Color = System.Drawing.ColorTranslator.ToWin32(Color.Green);
83: passed++;
84: }
85: else if (0 == unitTestResultType.outcome.CompareTo("Failed"))
86: {
87: oSheet.Rows.get_Range("A" + row.ToString(), "D" + row.ToString()).Interior.Color = System.Drawing.ColorTranslator.ToWin32(Color.Red);
88: failed++;
89: }
90: else if (0 == unitTestResultType.outcome.CompareTo("NotExecuted"))
91: {
92: oSheet.Rows.get_Range("A" + row.ToString(), "D" + row.ToString()).Interior.Color = System.Drawing.ColorTranslator.ToWin32(Color.SlateGray);
93: notexecuted++;
94: }
95: row++;
96: }
97: }
98: }
99: }
100: }
101:
102: row += 2;
103:
104: // Add summmary
105: oSheet.Cells[row++, 1] = "Testcases Passed = " + passed.ToString();
106: oSheet.Cells[row++, 1] = "Testcases Failed = " + failed.ToString();
107: oSheet.Cells[row++, 1] = "Testcases Aborted = " + aborted.ToString();
108: oSheet.Cells[row++, 1] = "Testcases NotExecuted = " + notexecuted.ToString();
109:
110: // Autoformat the sheet
111: oSheet.Rows.get_Range("A1","D"+row.ToString()).AutoFormat(Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic1,false, false, false, true, false, true);
112:
113: }
114: catch (Exception ex)
115: {
116: Console.WriteLine(ex.ToString());
117: }
118: }
119: }
I am getting an exception when I try to initialize XmlSerializer for type TestRunType. By digging through the inner exceptions, I finally got the message "Member 'GenericTestType.Items' hides inherited member 'BaseTestType.Items', but has different custom attributes."
ReplyDeleteAny idea how this can be solved?
TestRunType doesn't show to have any properties. I am seeing the same behavior as the above user. Any key to unlock?
ReplyDeleteI also get the same error:
ReplyDelete"Member 'GenericTestType.Items' hides inherited member 'BaseTestType.Items', but has different custom attributes."
I commented out the Items property in GenericTestType and CodedWebTestElementType and it worked great.
ReplyDeleteNo still its throwing exception
Deleteat System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
at System.Xml.Serialization.XmlSerializer.Deserialize(TextReader textReader)
at TrxReader.MainWindow.ReadTrxFile() in d:\TrxReader\TrxReader\MainWindow.xaml.cs:line 44
at TrxReader.MainWindow..ctor() in d:\TrxReader\TrxReader\MainWindow.xaml.cs:line 29
It's not Ok if one test case is executed several times with different input parameters.
ReplyDeleteIs the VSTST.CS file generated by xsd.exe tool unique for every .trx files? I have to prepare a tool which parses .trx file at some dynamically provided location each time it runs. Any pointers regarding the same??
ReplyDeleteThe schema for all TRX files is the same so you can use the same vstst.cs for all .trx files, generate it once and you are good to go.
DeleteThis comment has been removed by the author.
ReplyDelete"Thank you for this article because it’s really informative, I love reading your article.Tron Token Development
ReplyDelete