MSTest 或 TRX 到 HTML,带动画图表





5.00/5 (11投票s)
MS Test Result Viewer 是一个免费的开源库,用于将 MS Test 结果 (.trx) 文件转换为 HTML。它还允许您在测试容器项目 (.dll) 文件上执行 MS Test。此实用程序将使用简单的命令行参数以 HTML 格式生成测试报告,并提供出色的用户体验。
动画测试结果图表 | 测试结果错误窗口 | 打印报告 |
![]() | ![]() | ![]() |
介绍
此工具允许您生成 HTML 报告,同时您也可以在测试项目容器上执行 MS Test。它提供了许多优势,例如:它允许轻松地管理测试中的状态。您的设置和清理代码将在每次调用方法时运行,并且实例变量会自动重置。不像 NUnit 那样需要手动重置它们。事实上,MSTest 的许多其他优点都依赖于这个原则,我们稍后将看到。
关于 MSTest (来自 Microsoft 网站)。
“MSTest.exe 是用于运行测试的命令行命令。此命令具有多个选项,可用于自定义测试运行。您可以将这些选项中的许多结合使用;事实上,您必须将某些选项与其他人结合使用,如以下部分所述。您可以在 MSTest.exe 命令行上按任何顺序指定这些选项。”
更多详情: http://msdn.microsoft.com/en-us/library/ms182489%28v=vs.80%29.aspx
使用代码
- 先决条件:
- RGraph (http://www.rgraph.net)
- jQuery UI Layout Plug-in (http://layout.jquery-dev.net)
- jQuery Grid Plugin – jqGrid (http://www.trirand.net)
- MS Test (http://msdn.microsoft.com/en-us/library/ms182489%28v=vs.80%29.aspx)
- 类层次结构
- 变量声明 定义了 TableName、Columns、Tags、Attributes 和 ExecutionSleep 的常量,以及用于在多个方法中访问的一些变量。
#region "Variables"
const int SLEEP_EXECUTION_TIME = 200;
const string TAG_UNITTEST = "UnitTest";
const string TAG_ERRORINFO = "ErrorInfo";
const string TABLE_TESTMETHOD = "TestMethod";
const string TABLE_TESTRUN = "TestRun";
const string TABLE_UNITTESTRESULT = "UnitTestResult";
const string TABLE_TIMES = "Times";
const string COLUMN_CLASSNAME = "className";
const string COLUMN_NAME = "name";
const string COLUMN_RUNUSER = "runUser";
const string COLUMN_MESSAGE = "Message";
const string COLUMN_STACKTRACE = "StackTrace";
const string COLUMN_COUNTERS = "Counters";
const string COLUMN_TOTAL = "total";
const string COLUMN_PASSED = "passed";
const string COLUMN_FAILED = "failed";
const string COLUMN_INCONCLUSIVE = "inconclusive";
const string COLUMN_TESTNAME = "testName";
const string COLUMN_OUTCOME = "outcome";
const string COLUMN_DURATION = "duration";
const string COLUMN_CREATION = "creation";
const string COLUMN_CODEBASE = "codebase";
const string ATTRIBUTE_TESTMETHODID = "TestMethodID";
const string ATTRIBUTE_ID = "id";
const string ATTRIBUTE_TESTID = "testId";
const string FILENAME_TRX = "MSTestResult.trx";
private static TestEnvironmentInfo testEnvironmentInfo;
private static TestResultSummary testResultSummary;
private static List<TestProjects> testProjects;
static string projectChartDataValue = "";
static string classChartDataValue = "";
static string classChartDataText = "";
static string methoChartDataValue = "";
static string methoChartDataText = "";
static string methoChartDataColor = "";
static string folderPath = "";
static string trxFilePath = "";
static string MSTestExePathParam = "";
static string TestContainerFolderPathParam = "";
static string DestinationFolderParam = "";
static string LogFileParam = "";
static string HelpFileParam = "";
#endregion
- 属性 用于获取应用程序名称和版本号以显示在 HTML 报告中。
public static string Title
{
get { return GetValue<AssemblyTitleAttribute>(a => a.Title); }
}
public static string Version
{
get { return "1.0"; }
}
static string GetValue<T>(Func<T, string> getValue) where T : Attribute
{
T a = (T)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(T));
return a == null ? "" : getValue(a);
}
名称 | 标志 | 总结 |
MSTest Exe 文件路径 | /m |
您需要定义 MSTest.exe 的物理路径。仅当您没有 .trx 文件时才需要此参数。 |
测试容器文件夹路径 | /tc |
您需要定义测试容器文件 (.dll) 的物理路径。仅当您没有 .trx 文件时才需要此参数。 |
Trx 文件路径 | /t | 您需要定义 trx 文件的物理路径。 |
目标文件夹路径 | /d | 输出数据(HTML)文件将存储在此位置。如果路径不存在,它将自动创建。 |
帮助 | ? | 这将显示屏幕上的所有命令行参数 |
- 命令行用法
MSTestResultViewer.Consol.exe /t "d:\test.trx" /d "d:\HTMLReport"
- 变换 此方法将使用 xml 文件填充数据集,然后数据集将用于计算所有测试用例和环境细节的统计信息。
private static void Transform(string fileName)
{
XmlDocument xmlDoc = new XmlDocument();
if (File.Exists(fileName))
{
xmlDoc.Load(fileName);
XmlNodeList list = xmlDoc.GetElementsByTagName(TAG_UNITTEST);
foreach (XmlNode node in list)
{
XmlAttribute newAttr = xmlDoc.CreateAttribute(ATTRIBUTE_TESTMETHODID);
newAttr.Value = node.Attributes[ATTRIBUTE_ID].Value;
node.ChildNodes[1].Attributes.Append(newAttr);
}
list = xmlDoc.GetElementsByTagName(TAG_ERRORINFO);
foreach (XmlNode node in list)
{
XmlAttribute newAttr = xmlDoc.CreateAttribute(ATTRIBUTE_TESTMETHODID);
newAttr.Value = (((node).ParentNode).ParentNode).Attributes[ATTRIBUTE_TESTID].Value;
node.Attributes.Append(newAttr);
}
//xmlDoc.Save(fileName);
DataSet ds = new DataSet();
ds.ReadXml(new XmlNodeReader(xmlDoc));
if (ds != null && ds.Tables.Count >= 4)
{
Console.WriteLine(string.Format("Start gathering test environment information...\n"));
System.Threading.Thread.Sleep(SLEEP_EXECUTION_TIME);
SetTestEnvironmentInfo(ds);
Console.WriteLine(string.Format("Start gathering test result summary...\n"));
System.Threading.Thread.Sleep(SLEEP_EXECUTION_TIME);
SetTestResultSummary(ds);
Console.WriteLine(string.Format("Start gathering test classes methods information...\n"));
System.Threading.Thread.Sleep(SLEEP_EXECUTION_TIME);
SetTestClassMethods(ds);
if (testProjects.Count >= 1)
{
Console.WriteLine(string.Format("Start transforming test result into html...\n"));
System.Threading.Thread.Sleep(SLEEP_EXECUTION_TIME);
CreateTestHierarchy();
CreateTestResultTable();
CreateTestResultChart();
Console.WriteLine(string.Format("TRX file transformation completed successfully. \nFile generated at: \"{0}.htm\"\n", trxFilePath));
}
else
{
Console.WriteLine(string.Format("No test cases are available for test\n"));
Console.ReadLine();
}
}
else
{
Console.WriteLine(string.Format("No test cases are available for test\n"));
Console.ReadLine();
}
}
else
{
Console.WriteLine(string.Format("Test Result File (.trx) not found at \"" + trxFilePath + "\"!\n"));
Console.ReadLine();
}
}
private static void SetTestEnvironmentInfo(DataSet ds)
{
DataRow dr = ds.Tables[TABLE_TESTRUN].Rows[0];
string trxfile = dr[COLUMN_NAME].ToString();
int idxattherate = trxfile.IndexOf("@") + 1;
int idxspace = trxfile.IndexOf(" ");
string machine = trxfile.Substring(idxattherate, idxspace - idxattherate);
DateTime time = (ds.Tables[TABLE_TIMES] != null && ds.Tables[TABLE_TIMES].Rows.Count > 0) ? Convert.ToDateTime(ds.Tables[TABLE_TIMES].Rows[0][COLUMN_CREATION]) : DateTime.Now;
string codebase = (ds.Tables[TABLE_TESTMETHOD] != null && ds.Tables[TABLE_TESTMETHOD].Rows.Count > 0) ? ds.Tables[TABLE_TESTMETHOD].Rows[0][COLUMN_CODEBASE].ToString() : "";
testEnvironmentInfo = new TestEnvironmentInfo()
{
MachineName = machine,
OriginalTRXFile = trxfile,
TestCodebase = codebase,
UserName = dr[COLUMN_RUNUSER].ToString(),
Timestamp = time
};
}
private static void SetTestResultSummary(DataSet ds)
{
DataRow dr = ds.Tables[COLUMN_COUNTERS].Rows[0];
testResultSummary = new TestResultSummary()
{
Total = Convert.ToInt16(dr[COLUMN_TOTAL].ToString()),
Passed = Convert.ToInt16(dr[COLUMN_PASSED].ToString()),
Failed = Convert.ToInt16(dr[COLUMN_FAILED].ToString()),
Ignored = Convert.ToInt16(dr[COLUMN_INCONCLUSIVE].ToString()),
Duration = "--",
TestEnvironment = testEnvironmentInfo
};
}
private static void SetTestClassMethods(DataSet ds)
{
DataView view = new DataView(ds.Tables[TABLE_TESTMETHOD]);
DataTable distinctValues = view.ToTable(true, COLUMN_CLASSNAME);
char[] delimiters = new char[] { ',' };
//Getting Distinct Project
testProjects = new List<TestProjects>();
foreach (DataRow dr in distinctValues.Rows)
{
string _project = dr[COLUMN_CLASSNAME].ToString().Split(delimiters, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
int cnt = (from t in testProjects where t.Name == _project select t).Count();
if (cnt == 0) testProjects.Add(new TestProjects() { Name = _project.Trim() });
}
//Iterate through all the projects for getting its classes
foreach (TestProjects project in testProjects)
{
DataRow[] classes = distinctValues.Select(COLUMN_CLASSNAME + " like '% " + project.Name + ", %'");
if (classes != null && classes.Count() > 0)
{
project.Classes = new List<TestClasses>();
foreach (DataRow dr in classes)
{
string _class = dr[COLUMN_CLASSNAME].ToString().Split(delimiters, StringSplitOptions.RemoveEmptyEntries)[0].Trim();
project.Classes.Add(new TestClasses() { Name = _class });
}
}
}
//Iterate through all the projects and then classes to get test methods details
TimeSpan durationProject = TimeSpan.Parse("00:00:00.00");
foreach (TestProjects _project in testProjects)
{
Int32 _totalPassed = 0;
Int32 _totalFailed = 0;
Int32 _totalIgnored = 0;
foreach (TestClasses _class in _project.Classes)
{
TimeSpan durationClass = TimeSpan.Parse("00:00:00.00");
DataRow[] methods = ds.Tables[TABLE_TESTMETHOD].Select(COLUMN_CLASSNAME + " like '" + _class.Name + ", " + _project.Name + ", %'");
if (methods != null && methods.Count() > 0)
{
_class.Methods = new List<TestClassMethods>();
Int32 _passed = 0;
Int32 _failed = 0;
Int32 _ignored = 0;
foreach (DataRow dr in methods)
{
TimeSpan durationMethod = TimeSpan.Parse("00:00:00.00");
TestClassMethods _method = GetTestMethodDetails(ds, dr[ATTRIBUTE_TESTMETHODID].ToString());
switch (_method.Status.ToUpper())
{
case "PASSED":
_passed++;
break;
case "FAILED":
_failed++;
break;
default:
_ignored++;
break;
}
_class.Passed = _passed;
_class.Failed = _failed;
_class.Ignored = _ignored;
_class.Total = (_passed + _failed + _ignored);
_class.Methods.Add(_method);
durationClass += TimeSpan.Parse(_method.Duration);
}
}
_totalPassed += _class.Passed;
_totalFailed += _class.Failed;
_totalIgnored += _class.Ignored;
_class.Duration = durationClass.ToString();
durationProject += TimeSpan.Parse(_class.Duration);
}
_project.Passed = _totalPassed;
_project.Failed = _totalFailed;
_project.Ignored = _totalIgnored;
_project.Total = (_totalPassed + _totalFailed + _totalIgnored);
_project.Duration = durationProject.ToString();
durationProject += TimeSpan.Parse(_project.Duration);
}
}
private static TestClassMethods GetTestMethodDetails(DataSet ds, string testID)
{
TestClassMethods _method = null;
DataRow[] methods = ds.Tables[TABLE_UNITTESTRESULT].Select(ATTRIBUTE_TESTID + "='" + testID + "'");
if (methods != null && methods.Count() > 0)
{
_method = new TestClassMethods();
foreach (DataRow dr in methods)
{
_method.Name = dr[COLUMN_TESTNAME].ToString();
_method.Status = dr[COLUMN_OUTCOME].ToString();//(Enums.TestStatus)Enum.Parse(typeof(Enums.TestStatus), dr[COLUMN_OUTCOME].ToString());
_method.Error = GetErrorInfo(ds, testID);
_method.Duration = dr[COLUMN_DURATION].ToString();
}
}
return _method;
}
private static ErrorInfo GetErrorInfo(DataSet ds, string testID)
{
ErrorInfo _error = null;
DataRow[] errorMethod = ds.Tables[TAG_ERRORINFO].Select(ATTRIBUTE_TESTMETHODID + "='" + testID + "'");
if (errorMethod != null && errorMethod.Count() > 0)
{
_error = new ErrorInfo();
string[] delimiters = new string[] { ":line " };
foreach (DataRow dr in errorMethod)
{
_error.Message = dr[COLUMN_MESSAGE].ToString();
_error.StackTrace = dr[COLUMN_STACKTRACE].ToString();
string strLineNo = _error.StackTrace.Split(delimiters, StringSplitOptions.RemoveEmptyEntries)[1];
Int32 LineNo = Convert.ToInt32(strLineNo);
_error.LineNo = LineNo;
}
}
return _error;
}
private static void CreateTestHierarchy()
{
StringBuilder sb = new StringBuilder();
sb.Append("var treeData = '");
foreach (var _project in testProjects)
{
sb.Append("<li><span class=\"testProject\">" + _project.Name + "</span>");
sb.Append("<ul>");
foreach (var _class in _project.Classes)
{
//Remove project name from name space if exists.
string classname = _class.Name;
string projectname = _project.Name + ".";
string[] tmp = _class.Name.Split(new string[] { projectname }, StringSplitOptions.RemoveEmptyEntries);
if (tmp.Length >= 2)
classname = (tmp[0] == _project.Name) ? ConvertStringArrayToString(tmp, 1) : tmp[0];
else if (tmp.Length == 1)
classname = tmp[0];
sb.Append("<li><span class=\"testClass\">" + classname + "</span>");
sb.Append("<ul>");
foreach (var _method in _class.Methods)
{
string imgStatus = "StatusFailed";
switch (_method.Status)
{
case "Passed":
imgStatus = "StatusPassed";
break;
case "Ignored":
imgStatus = "StatusIngnored";
break;
case "Failed":
imgStatus = "StatusFailed";
break;
}
sb.Append("<li><span class=\"testMethod\">" + _method.Name + "<img src=\"Images/" + imgStatus + ".png\" height=\"10\" width=\"10\" /></span></li>");
}
sb.Append("</ul>");
sb.Append("</li>");
}
sb.Append("</ul>");
sb.Append("</li>");
}
sb.Append("';");
string htmlTestHierarchy = sb.ToString();
WriteFile("TestHierarchy.js", htmlTestHierarchy);
}
private static string ConvertStringArrayToString(string[] array, int startIndex)
{
StringBuilder builder = new StringBuilder();
for (int i = startIndex; i < array.Length; i++)
{
builder.Append(array[i]);
}
return builder.ToString();
}
当单击任何测试方法时,将出现一个错误窗口(页面底部),其中包含完整的错误信息。
同样,此方法将创建一个动态的 javascript 文件 (Table.js) 以树状网格格式显示数据。
private static void CreateTestResultTable()
{
try
{
StringBuilder sbenv = new StringBuilder();
sbenv.Append("var environment = {");
sbenv.Append("'TestCodebase':'" + testEnvironmentInfo.TestCodebase + "',");
sbenv.Append("'Timestamp':'" + testEnvironmentInfo.Timestamp + "',");
sbenv.Append("'MachineName':'" + testEnvironmentInfo.MachineName + "',");
sbenv.Append("'UserName':'" + testEnvironmentInfo.UserName + "',");
sbenv.Append("'OriginalTRXFile':'" + testEnvironmentInfo.OriginalTRXFile + "'");
sbenv.Append("};");
WriteFile("Environment.js", sbenv.ToString());
StringBuilder sb = new StringBuilder();
sb.Append("$(function () {");
sb.Append(" $('#dvTestCodebase').text(environment.TestCodebase);");
sb.Append(" $('#dvGeneratedDate').text(environment.Timestamp);");
sb.Append(" $('#dvMachineName').text(environment.MachineName);");
sb.Append(" $('#dvUserName').text(environment.UserName);");
sb.Append(" $('#dvTRXFileName').text(environment.OriginalTRXFile);");
sb.Append("var mydata = [");
int counter = 0;
foreach (var _project in testProjects)
{
counter++;
int total = _project.Passed + _project.Failed + _project.Ignored;
double percentPass = (_project.Passed * 100);
if (percentPass > 0) percentPass = percentPass / total;
double percentFail = (_project.Failed * 100);
if (percentFail > 0) percentFail = percentFail / total;
double percentIgnore = (_project.Ignored * 100);
if (percentIgnore > 0) percentIgnore = percentIgnore / total;
string strPercent = string.Format("{0},{1},{2}", percentPass, percentFail, percentIgnore);
string strProject = string.Format("{{id: \"{0}\", parent: \"{1}\", level: \"{2}\", Name: \"{3}\", Passed: \"{4}\", Failed: \"{5}\", Ignored: \"{6}\", Percent: \"{7}\", Progress: \"{8}\", Time: \"{9}\", Message: \"{10}\", StackTrace: \"{11}\", LineNo: \"{12}\", isLeaf: {13}, expanded: {14}, loaded: {15}}},", counter, "", "0", _project.Name, _project.Passed, _project.Failed, _project.Ignored, string.Format("{0:00.00}", percentPass), strPercent, TimeSpan.Parse(_project.Duration).TotalMilliseconds, "", "", "", "false", "true", "true");
sb.Append(strProject);
int projParent = counter;
projectChartDataValue = "var projectData = [" + _project.Passed + ", " + _project.Failed + ", " + _project.Ignored + "];";
foreach (var _class in _project.Classes)
{
counter++;
total = _class.Passed + _class.Failed + _class.Ignored;
percentPass = (_class.Passed * 100);
if (percentPass > 0) percentPass = percentPass / total;
percentFail = (_class.Failed * 100);
if (percentFail > 0) percentFail = percentFail / total;
percentIgnore = (_class.Ignored * 100);
if (percentIgnore > 0) percentIgnore = percentIgnore / total;
strPercent = string.Format("{0},{1},{2}", percentPass, percentFail, percentIgnore);
//Remove project name from name space if exists.
string classname = _class.Name;
string projectname = _project.Name + ".";
string[] tmp = _class.Name.Split(new string[] { projectname }, StringSplitOptions.RemoveEmptyEntries);
if (tmp.Length >= 2)
classname = (tmp[0] == _project.Name) ? ConvertStringArrayToString(tmp, 1) : tmp[0];
else if (tmp.Length == 1)
classname = tmp[0];
string strClass = string.Format("{{id: \"{0}\", parent: \"{1}\", level: \"{2}\", Name: \"{3}\", Passed: \"{4}\", Failed: \"{5}\", Ignored: \"{6}\", Percent: \"{7}\", Progress: \"{8}\", Time: \"{9}\", Message: \"{10}\", StackTrace: \"{11}\", LineNo: \"{12}\", isLeaf: {13}, expanded: {14}, loaded: {15}}},", counter, projParent, "1", classname, _class.Passed, _class.Failed, _class.Ignored, string.Format("{0:00.00}", percentPass), strPercent, TimeSpan.Parse(_class.Duration).TotalMilliseconds, "", "", "", "false", "true", "true");
sb.Append(strClass);
int classParent = counter;
classChartDataValue += "[" + _class.Passed + ", " + _class.Failed + ", " + _class.Ignored + "],";
classChartDataText += "'" + classname + "',";
foreach (var _method in _class.Methods)
{
counter++;
int _passed = 0;
int _failed = 0;
int _ignored = 0;
percentPass = 0.0;
strPercent = "";
methoChartDataValue += TimeSpan.Parse(_method.Duration).TotalMilliseconds + ",";
methoChartDataText += "'" + _method.Name + "',";
switch (_method.Status)
{
case "Passed":
_passed = 1;
percentPass = 100;
strPercent = "100,0,0";
methoChartDataColor += "testResultColor[0],";
break;
case "Failed":
_failed = 1;
strPercent = "0,100,0";
methoChartDataColor += "testResultColor[1],";
break;
case "Ignored":
_ignored = 1;
strPercent = "0,0,100";
methoChartDataColor += "testResultColor[2],";
break;
}
string strError = "";
string strStack = "";
string strLine = "";
if (_method.Error != null)
{
strError = _method.Error.Message.Replace("\r\n", "").Replace("\"", "'");
strStack = _method.Error.StackTrace.Replace("\r\n", "").Replace("\"", "'");
strLine = _method.Error.LineNo.ToString();
}
string strMethod = string.Format("{{id: \"{0}\", parent: \"{1}\", level: \"{2}\", Name: \"{3}\", Passed: \"{4}\", Failed: \"{5}\", Ignored: \"{6}\", Percent: \"{7}\", Progress: \"{8}\", Time: \"{9}\", Message: \"{10}\", StackTrace: \"{11}\", LineNo: \"{12}\", isLeaf: {13}, expanded: {14}, loaded: {15}}},", counter, classParent, "2", _method.Name, _passed, _failed, _ignored, string.Format("{0:00.00}", percentPass), strPercent, TimeSpan.Parse(_method.Duration).TotalMilliseconds, strError, strStack, strLine, "true", "false", "true");
sb.Append(strMethod);
}
}
classChartDataValue = "var classDataValue = [" + classChartDataValue + "];";
classChartDataText = "var classDataText = [" + classChartDataText + "];";
methoChartDataValue = "var methoDataValue = [" + methoChartDataValue + "];";
methoChartDataText = "var methoDataText = [" + methoChartDataText + "];";
methoChartDataColor = "var methoDataColor = [" + methoChartDataColor + "];";
}
sb.Append("],");
sb.Append("getColumnIndexByName = function (grid, columnName) {");
sb.Append("var cm = grid.jqGrid('getGridParam', 'colModel');");
sb.Append("for (var i = 0; i < cm.length; i += 1) {");
sb.Append("if (cm[i].name === columnName) {");
sb.Append("return i;");
sb.Append("}");
sb.Append("}");
sb.Append("return -1;");
sb.Append("},");
sb.Append("grid = $('#treegrid');");
sb.Append("grid.jqGrid({");
sb.Append("datatype: 'jsonstring',");
sb.Append("datastr: mydata,");
sb.Append("colNames: ['Id', 'Name', 'Passed', 'Failed', 'Ignored', '%', '', 'Time', 'Message','StackTrace','LineNo'],");
sb.Append("colModel: [");
sb.Append("{ name: 'id', index: 'id', width: 1, hidden: true, key: true },");
sb.Append("{ name: 'Name', index: 'Name', width: 380 },");
sb.Append("{ name: 'Passed', index: 'Passed', width: 70, align: 'right', formatter: testCounterFormat },");
sb.Append("{ name: 'Failed', index: 'Failed', width: 70, align: 'right', formatter: testCounterFormat },");
sb.Append("{ name: 'Ignored', index: 'Ignored', width: 70, align: 'right', formatter: testCounterFormat },");
sb.Append("{ name: 'Percent', index: 'Percent', width: 50, align: 'right' },");
sb.Append("{ name: 'Progress', index: 'Progress', width: 200, align: 'right', formatter: progressFormat },");
sb.Append("{ name: 'Time', index: 'Time', width: 75, align: 'right'},");
sb.Append("{ name: 'Message', index: 'Message', hidden: true, width: 100, align: 'right'},");
sb.Append("{ name: 'StackTrace', index: 'StackTrace', hidden: true, width: 100, align: 'right'},");
sb.Append("{ name: 'LineNo', index: 'LineNo', width: 100, hidden: true, align: 'right'}],");
sb.Append("height: 'auto',");
sb.Append("gridview: true,");
sb.Append("rowNum: 10000,");
sb.Append("sortname: 'id',");
sb.Append("treeGrid: true,");
sb.Append("treeGridModel: 'adjacency',");
sb.Append("treedatatype: 'local',");
sb.Append("ExpandColumn: 'Name',");
sb.Append("ondblClickRow: function(id) {");
sb.Append("parent.innerLayout.open('south');");
sb.Append("setErrorInfo(id);");
sb.Append("},");
sb.Append("onSelectRow: function(id){");
sb.Append("setErrorInfo(id);");
sb.Append("},");
sb.Append("jsonReader: {");
sb.Append("repeatitems: false,");
sb.Append("root: function (obj) { return obj; },");
sb.Append("page: function (obj) { return 1; },");
sb.Append("total: function (obj) { return 1; },");
sb.Append("records: function (obj) { return obj.length; }");
sb.Append("}");
sb.Append("});");
sb.Append("function setErrorInfo(id) {");
sb.Append("var doc = $('#tblError', top.document);");
sb.Append("doc.find('#dvErrorMessage').text($('#treegrid').getRowData(id)['Message']);");
sb.Append("doc.find('#dvLineNumber').text($('#treegrid').getRowData(id)['LineNo']);");
sb.Append("doc.find('#dvStackTrace').text($('#treegrid').getRowData(id)['StackTrace']);");
sb.Append("}");
sb.Append("function progressFormat(cellvalue, options, rowObject) {");
sb.Append("var progress = cellvalue.split(',');");
sb.Append("var pass = Math.round(progress[0]) * 2;");
sb.Append("var fail = Math.round(progress[1]) * 2;");
sb.Append("var ignore = Math.round(progress[2]) * 2;");
sb.Append("var strProgress = \"<div class='ProgressWrapper'>");
sb.Append("<div class='ProgressPass' title='\"+ Number(progress[0]).toFixed(2) +\"% Passed' style='width: \" + pass + \"px'></div>");
sb.Append("<div class='ProgressFail' title='\"+ Number(progress[1]).toFixed(2) +\"% Failed' style='width: \" + fail + \"px'></div>");
sb.Append("<div class='ProgressIgnore' title='\"+ Number(progress[2]).toFixed(2) +\"% Ignored' style='width: \" + ignore + \"px'></div>");
sb.Append("</div>\";");
sb.Append("return strProgress;");
sb.Append("}");
sb.Append("function testCounterFormat(cellvalue, options, rowObject) {");
sb.Append("return cellvalue;");
sb.Append("}");
sb.Append("grid.jqGrid('setLabel', 'Passed', '', { 'text-align': 'right' });");
sb.Append("grid.jqGrid('setLabel', 'Failed', '', { 'text-align': 'right' });");
sb.Append("grid.jqGrid('setLabel', 'Ignored', '', { 'text-align': 'right' });");
sb.Append("grid.jqGrid('setLabel', 'Percent', '', { 'text-align': 'right' });");
sb.Append("});");
string xmlTestResultTable = sb.ToString().Replace("},],", "}],");
WriteFile("Table.js", xmlTestResultTable);
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message);
}
}
private static void CreateTestResultChart()
{
StringBuilder sb = new StringBuilder();
sb.Append("var testResultStatus = ['Passed', 'Failed', 'Ignored'];");
sb.Append("var testResultColor = ['#ABD874', '#E18D87', '#F4AD7C'];");
sb.Append(projectChartDataValue);
sb.Append(classChartDataValue);
sb.Append(classChartDataText);
sb.Append(methoChartDataValue);
sb.Append(methoChartDataText);
sb.Append(methoChartDataColor);
string xmlTestResultTable = sb.ToString().Replace(",]", "]");
WriteFile("Chart.js", xmlTestResultTable);
}
private static void WriteFile(string FileName, string FileContent)
{
using (System.IO.StreamWriter file = new System.IO.StreamWriter(Path.Combine(folderPath, FileName)))
{
file.WriteLine(FileContent);
}
}
此方法将使用三个参数创建一个动态的批处理文件 (mstestrunner.bat)。使用这些参数,它将从应用程序调用以在测试项目容器上执行 MS Test。成功完成批处理命令后,它将创建一个新文件 (.trx) 以获取测试项目的统计信息。
private static void GenerateTRXFile(string MsTestExePath, string TestContainerFilePath)
{
trxFilePath = Path.Combine(folderPath, FILENAME_TRX);
string commandText = "\"" + MsTestExePath + "\" /testcontainer:\"" + TestContainerFilePath + "\" /resultsfile:\"" + trxFilePath + "\"";
WriteFile("mstestrunner.bat", commandText);
ExecuteBatchFile();
}
new Process()
命令。private static void ExecuteBatchFile()
{
Process process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.FileName = Path.Combine(folderPath, "mstestrunner.bat");
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
}
private static bool RecognizeParameters(string[] args)
{
if (args.Length >= 4 && args.Length <= 10)
{
int i = 0;
while (i < args.Length)
{
switch (args[i].ToLower())
{
case "/m":
case "/mstestexepath":
case "-m":
case "-mstestexepath":
if (args.Length > i)
{
MSTestExePathParam = args[i + 1];
i += 2;
}
else
return false;
break;
case "/tc":
case "/testcontainerfolderpath":
case "-tc":
case "-testcontainerfolderpath":
if (args.Length > i)
{
TestContainerFolderPathParam = args[i + 1];
i += 2;
}
else
return false;
break;
case "/t":
case "/trxfilepath":
case "-t":
case "-trxfilepath":
if (args.Length > i)
{
trxFilePath = args[i + 1];
i += 2;
}
else
return false;
break;
case "/d":
case "/destinationfolderpath":
case "-d":
case "-destinationfolderpath":
if (args.Length > i)
{
DestinationFolderParam = args[i + 1];
i += 2;
}
else
return false;
break;
case "/?":
case "/help":
case "-?":
case "-help":
return false;
default:
Console.WriteLine("Error: Unrecognized parameter\n");
return false;
}
}
return true;
}
return false;
}
MSTestResultViewer.Consol.exe /?h
代码private static void DisplayCommandLineHelp()
{
StringBuilder sb = new StringBuilder();
sb.Append(string.Format("{0}.exe\n", Title));
sb.Append(string.Format("[</M MSTestExePath>\n"));
sb.Append(string.Format(" </TC TestContainerFolderPath>] :optional if you set /T TRXFilePath\n"));
sb.Append(string.Format("[</T TRXFilePath>]\n"));
sb.Append(string.Format("</D DestinationFolder> :not available\n"));
sb.Append(string.Format("</L LogFile> :not available\n"));
sb.Append(string.Format("</H HelpFile> :not available\n"));
sb.Append(string.Format("</? Help>\n"));
Console.Write(sb.ToString());
Console.ReadKey();
}
private static bool CopyFilesWithSubFolders(string SourcePath, string DestinationPath, bool overwriteexisting, string Pattern = "*.*")
{
bool ret = true;
try
{
SourcePath = SourcePath.EndsWith(@"\") ? SourcePath : SourcePath + @"\";
DestinationPath = DestinationPath.EndsWith(@"\") ? DestinationPath : DestinationPath + @"\";
if (Directory.Exists(SourcePath))
{
if (Directory.Exists(DestinationPath) == false) Directory.CreateDirectory(DestinationPath);
foreach (string fls in Directory.GetFiles(SourcePath, Pattern))
{
FileInfo flinfo = new FileInfo(fls);
flinfo.CopyTo(DestinationPath + flinfo.Name, overwriteexisting);
}
foreach (string drs in Directory.GetDirectories(SourcePath))
{
DirectoryInfo drinfo = new DirectoryInfo(drs);
if (CopyFilesWithSubFolders(drs, DestinationPath + drinfo.Name, overwriteexisting, Pattern) == false) ret = false;
}
}
else
{
ret = false;
}
}
catch (Exception ex)
{
ret = false;
}
return ret;
}
- MsTestResultViewer 是免费的,我希望您觉得它很有用。如果您想支持未来的开发和新产品功能,请通过评论、邮件、消息、点赞或分享来留下您的宝贵评论、反馈、建议或赞赏。
这些努力用于在时间范围内覆盖和提高产品效率。
- 第三方通知和许可证信息
- RGraph (http://www.rgraph.net) RGraph 可免费用于非商业网站,例如个人博客、教育或慈善网站 - 您无需购买许可证 - 只需在您自己的网站上链接到 RGraph 网站即可。完整的源代码包含在下载中,您可以根据需要进行编辑。
- jQuery UI Layout Plug-in (http://layout.jquery-dev.net) 该插件的灵感来自 extJS 的 border-layout,并作为 jQuery 插件重新实现了该功能。UI Layout 插件可以创建您想要的任何 UI 外观 - 从简单的标题或侧边栏,到具有工具栏、菜单、帮助面板、状态栏、子表单等的复杂应用程序。
- jQuery Grid Plugin – jqGrid (http://www.trirand.net/licensing.aspx) 该插件已并将始终在最宽松和免费的 MIT 许可证下获得许可。但是,许多客户和组织需要商业级别的许可证、支持和功能。
- 许可证 开源许可证通常也是免费的,允许修改、重新分发和商业用途,而无需向原作者付费。这是开源软件,从 Web App Development Company Blog,您将获得 MsTestResultViewer 的更新安装程序、源代码和完整的用法指南文档。