65.9K
CodeProject 正在变化。 阅读更多。
Home

MSTest 或 TRX 到 HTML,带动画图表

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2012年10月16日

CPOL

6分钟阅读

viewsIcon

96988

MS Test Result Viewer 是一个免费的开源库,用于将 MS Test 结果 (.trx) 文件转换为 HTML。它还允许您在测试容器项目 (.dll) 文件上执行 MS Test。此实用程序将使用简单的命令行参数以 HTML 格式生成测试报告,并提供出色的用户体验。

动画测试结果图表测试结果错误窗口打印报告
MSTest result in Charts  MSTest Error Window Print Report

介绍  

此工具允许您生成 HTML 报告,同时您也可以在测试项目容器上执行 MS Test。它提供了许多优势,例如:它允许轻松地管理测试中的状态。您的设置和清理代码将在每次调用方法时运行,并且实例变量会自动重置。不像 NUnit 那样需要手动重置它们。事实上,MSTest 的许多其他优点都依赖于这个原则,我们稍后将看到。

关于 MSTest (来自 Microsoft 网站)。

“MSTest.exe 是用于运行测试的命令行命令。此命令具有多个选项,可用于自定义测试运行。您可以将这些选项中的许多结合使用;事实上,您必须将某些选项与其他人结合使用,如以下部分所述。您可以在 MSTest.exe 命令行上按任何顺序指定这些选项。”

更多详情:  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);
    }
  • 命令行参数
  • 这是应用程序的主函数或启动函数,它需要一些参数才能成功生成 HTML 报告。


    名称 标志 总结 
    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();
          }
      }
    • SetTestEnvironmentInfo
    • 此函数将获取测试机器环境信息,如机器名称、原始 TRX 文件名、用户名、时间戳等。
      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
          };
      }
    • SetTestResultSummary
    • 此函数显示测试结果的总体统计信息,如通过、失败、已忽略和持续时间。
      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
          };
      }
    • SetTestClassMethods
    • 这将创建层次结构,以便生成测试项目、测试类和测试方法及其统计信息。
      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);
          }
      }
    • GetTestMethodDetails
    • 此方法将返回测试用例结果(如果失败)。它将返回有关错误描述、堆栈跟踪、方法名称和行号的信息。
      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;
      }
    • GetErrorInfo
    • 这是获取测试结果信息的辅助方法。
      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;
      }
    • CreateTestHierarchy
    • 这将创建您的测试项目的层次结构,并按时间顺序显示每个测试方法的状态。例如,首先显示测试项目名称,然后列出测试项目下的所有测试类,然后列出与测试类相关的所有测试方法。并创建一个动态的 javascript (TestHierarchy.js) 文件以在 HTML 报告中显示树形结构。
      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);
      }
    • ConvertStringArrayToString
    • 这是辅助方法,用于从数组中多个项目的列表中获取单个字符串。它将数组元素连接成一个单一的字符串。
      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();
      }
    • CreateTestResultTable
    • 此方法将在树形和网格(高级表格)格式中显示有关您的测试容器的完整信息,包括通过、失败和已忽略方法的总数。并提供额外信息,如执行方法所需总时间、测试方法状态的进度指示器(例如,是否通过或失败)。
      当单击任何测试方法时,将出现一个错误窗口(页面底部),其中包含完整的错误信息。
      同样,此方法将创建一个动态的 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);
          }
      }
    • CreateTestResultChart
    • 此方法将创建一个动态的 javascript (Chart.js) 文件以显示动画图表(饼图和条形图)。
      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);
      }
    • WriteFile
    • 这是辅助方法,它会将内容写入文件并保存到目标文件夹。
      private static void WriteFile(string FileName, string FileContent)
      {
          using (System.IO.StreamWriter file = new System.IO.StreamWriter(Path.Combine(folderPath, FileName)))
          {
              file.WriteLine(FileContent);
          }
      }
    • GenerateTRXFile
    • 这是此应用程序非常重要的方法。如果您没有 TRX 文件,它将允许您选择使用测试容器项目 (.dll) 生成 trx 文件。
      此方法将使用三个参数创建一个动态的批处理文件 (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();
      }
    • 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();
      }
    • RecognizeParameters
    • 此方法仅负责验证用户传入的参数。
      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;
      }
    • DisplayCommandLineHelp
    • 这将显示有关每个参数的帮助信息。使用以下命令,您将获得此应用程序的完整帮助。
      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();
      }
    • CopyFilesWithSubFolders
    • 这是一个辅助方法,负责将任何文件和文件夹从源复制到目标。如果源文件夹不存在,它将自动创建。
      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 花费了大量的精力和时间——现在仍然如此。

    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 的更新安装程序、源代码和完整的用法指南文档。

     
© . All rights reserved.