Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / C#

MSTest Rerun Failed Tests Through MSTest.exe Wrapper Application

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
25 Feb 2016Ms-PL5 min read 26.3K   12   8
If you need to rerun Failed Tests (MSTest), you can download the application that I wrote. Examples how to use it and code snippets.

Introduction

Sometimes, you have the need to rerun failed tests of your application. For example, if you have a large number of System or UI tests which tend to be more fragile.

In my work, we use MSTest as main unit/integration/UI tests framework. However, MSTest doesn’t come with any built-in feature for rerunning failed tests through its console runner.

Rerun Failed Tests Approaches

One way to accomplish the task is to extend the MSTest Execution Flow. However, there is very little information about the matter.

If you consider this approach maybe you will find these articles useful:

Extending the Visual Studio Unit Test Type, part 1 and Extending the Visual Studio Unit Test Type, part 2.

If you read these blog posts you will find out that it is a really difficult job to extend the MSTest Execution Flow and in my opinion is not the best solution.

I came up with a different answer to the problem- a console application which wraps mstest.exe and adds additional command line arguments like “\retriesCount:” and “\threshold:“. Here I will share with you the code of the application.

How To Rerun Failed Tests

1. Download the application. Download

2. Extract the files

3. Open MSTest.Console.Extended.exe.config

XML
<appSettings>
    <add key="MSTestConsoleRunnerPath" value="C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\mstest.exe" />
  </appSettings>

For the value set the correct path to the mstest.exe on the machine on which you are going to run your tests.

4. Open log4net.config

XML
<param name="File" value="C:/mstest/MSTest.Console.Extended.log"/>

Here set the default location for the application’s log. Later, there you will find detailed information about the executed actions.

5. Set correct command line arguments

If you want to start test execution via mstest.exe, you can use the following line:

"C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\mstest.exe" /resultsfile:"D:\Results.trx" /testcontainer:"D:\TestEGNExtractor.dll" /nologo

If you want to use MSTest.Console.Extended.exe in order to be able to rerun failed tests, you can use the following command:

MSTest.Console.Extended.exe /resultsfile:"D:\Results.trx" /testcontainer:"D:\TestEGNExtractor.dll" /nologo /retriesCount:3 /deleteOldResultsFiles:true /newResultsfile:"D:\Results1.trx" /threshold:5

There are 4 new arguments added.

/retriesCount:3 –  tells the application to retry the failed tests 3 times

/deleteOldResultFiles:true – if set to true and the specified test result files exist, they will be deleted if possible

/newResultsFile:”…” – the original test results file will not be modified. A new updated version will be created where all passed tests from the new test runs will be set.

/threshold:5 – If the percentage of the failed tests is bigger than 5% no next reruns will be performed.

Below you can find an overview of the actions performed in the application.

Image 1

 

First the specified console arguments will be parsed. The extended arguments will be removed. If you set deleteOldTestResults to true, the old test result files will be deleted if they exist. Next the mstest.exe will be started with the specified arguments. The program will wait for mstest.exe to exit. Next will deserialize the trx test results file to C# objects and will get all failed tests. If there are any failed tests, their percentage is smaller than the specified threshold and the retriestCount is larger than 1, failed tests will be rerun. A new mstest.exe run will be started. After that, the initial testRun’s results will be updated. If there are still any failed tests and the previously mentioned conditions are again met, a new rerun will be conducted. If you have specified newTestResultsFile, the newly updated testRun results will be serialized in it. Otherwise, the original trx file will be updated. If there are any failed tests, the application will exit with code 1 else with code 0.

MSTest.Console.Extended Code Organization

The code follows Onion Pattern, which allows it to be 100% unit testable.

Image 2

 

The first project contains the core logic for rerun of failed tests, the second one holds all unit tests.

C#
public class Program
{
    private static readonly ILog log = LogManager.GetLogger(typeof(Program));

    public static void Main(string[] args)
    {
        string microsoftTestConsoleExePath = ConfigurationManager.AppSettings["MSTestConsoleRunnerPath"]; 
        var consoleArgumentsProvider = new ConsoleArgumentsProvider(args);
        var engine = new TestExecutionService(
            new MsTestTestRunProvider(consoleArgumentsProvider, LogManager.GetLogger(typeof(MsTestTestRunProvider))),
            new FileSystemProvider(consoleArgumentsProvider),
            new ProcessExecutionProvider(microsoftTestConsoleExePath, consoleArgumentsProvider, LogManager.GetLogger(typeof(ProcessExecutionProvider))),
            consoleArgumentsProvider,
            LogManager.GetLogger(typeof(TestExecutionService)));
        try
        {
            int result = engine.ExecuteWithRetry();
            Environment.Exit(result);
        }
        catch (Exception ex)
        {
            log.Error(string.Concat(ex.Message, ex.StackTrace));
        }
    }
}

As you can see from the Main method of the MSTest.Console.Extended project, the main business logic is located in the TestExecutionService class. Through Inversion of Control, it uses the different Provider classes located under the Infrastructure folder.

Below you can find the main part of the TestExecutionService class which follows the logic described in the above diagram.

C#
public class TestExecutionService
{
    private readonly ILog log;

    private readonly IMsTestTestRunProvider microsoftTestTestRunProvider;

    private readonly IFileSystemProvider fileSystemProvider;

    private readonly IProcessExecutionProvider processExecutionProvider;

    private readonly IConsoleArgumentsProvider consoleArgumentsProvider;

    public TestExecutionService(
        IMsTestTestRunProvider microsoftTestTestRunProvider,
        IFileSystemProvider fileSystemProvider,
        IProcessExecutionProvider processExecutionProvider,
        IConsoleArgumentsProvider consoleArgumentsProvider,
        ILog log)
    {
        this.microsoftTestTestRunProvider = microsoftTestTestRunProvider;
        this.fileSystemProvider = fileSystemProvider;
        this.processExecutionProvider = processExecutionProvider;
        this.consoleArgumentsProvider = consoleArgumentsProvider;
        this.log = log;
    }
        
    public int ExecuteWithRetry()
    {
        this.fileSystemProvider.DeleteTestResultFiles();
        this.processExecutionProvider.ExecuteProcessWithAdditionalArguments();
        this.processExecutionProvider.CurrentProcessWaitForExit();
        var testRun = this.fileSystemProvider.DeserializeTestRun();
        int areAllTestsGreen = 1;
        var failedTests = new List<TestRunUnitTestResult>();
        failedTests = this.microsoftTestTestRunProvider.GetAllFailedTests(testRun.Results.ToList());
        int failedTestsPercentage = this.microsoftTestTestRunProvider.CalculatedFailedTestsPercentage(failedTests, testRun.Results.ToList());
        if (failedTestsPercentage < this.consoleArgumentsProvider.FailedTestsThreshold)
        {
            for (int i = 0; i < this.consoleArgumentsProvider.RetriesCount - 1; i++)
            {
                this.log.InfoFormat("Start to execute again {0} failed tests.", failedTests.Count);
                if (failedTests.Count > 0)
                {
                    string currentTestResultPath = this.fileSystemProvider.GetTempTrxFile();
                    string retryRunArguments = this.microsoftTestTestRunProvider.GenerateAdditionalArgumentsForFailedTestsRun(failedTests, currentTestResultPath);
                    this.log.InfoFormat("Run {0} time with arguments {1}", i + 2, retryRunArguments);
                    this.processExecutionProvider.ExecuteProcessWithAdditionalArguments(retryRunArguments);
                    this.processExecutionProvider.CurrentProcessWaitForExit();
                    var currentTestRun = this.fileSystemProvider.DeserializeTestRun(currentTestResultPath);
                    var passedTests = this.microsoftTestTestRunProvider.GetAllPassesTests(currentTestRun);
                    this.microsoftTestTestRunProvider.UpdatePassedTests(passedTests, testRun.Results.ToList());
                    areAllTestsGreen = 1;
                    this.microsoftTestTestRunProvider.UpdateResultsSummary(testRun);
                }
                else
                {
                    break;
                }
                failedTests = this.microsoftTestTestRunProvider.GetAllFailedTests(testRun.Results.ToList());
            }
        }
           
        if (testRun.ResultSummary.outcome == "Passed")
        {
            areAllTestsGreen = 0;
        }
        this.fileSystemProvider.SerializeTestRun(testRun);

        return areAllTestsGreen;
    }
}

As you can see, it only implements the desired algorithm but all of the specific logic, like argument parsing, file system operations, test run operations, is located in the Provider classes.

 ConsoleArugmentsProvider Class

Image 3

 

The ConsoleArgumentsProvider class contains the main logic for parsing the specified console arguments. Also, it implements the IConsoleArgumentsProvider interface, which helps us to mock it easily. It uses the below regex expressions to extract the different arguments and their values.

C#
private readonly string testResultFilePathRegexPattern = @".*resultsfile:(?<ResultsFilePath>[1-9A-Za-z\\:._]{1,})";
        private readonly string testNewResultFilePathRegexPattern = @".*(?<NewResultsFilePathArgument>/newResultsfile:(?<NewResultsFilePath>[1-9A-Za-z\\:._]{1,}))";
        private readonly string retriesRegexPattern = @".*(?<RetriesArgument>/retriesCount:(?<RetriesCount>[0-9]{1})).*";
        private readonly string failedTestsThresholdRegexPattern = @".*(?<ThresholdArgument>/threshold:(?<ThresholdCount>[0-9]{1})).*";
        private readonly string deleteOldFilesRegexPattern = @".*(?<DeleteOldFilesArgument>/deleteOldResultsFiles:(?<DeleteOldFilesValue>[a-zA-Z]{4,5})).*";
        private readonly string argumentRegexPattern = @".*/(?<ArgumentName>[a-zA-Z]{1,}):(?<ArgumentValue>.*)";

 

 FileSystemProvider Class

The below class holds the main logic for serialization and deserialization of the mstest’s test result trx files into C# objects. It implements the IFileSystemProvider interface.

Image 4

 

You can find all classes related to the mstest test runs under the Data folder.

Image 5

 

MsTestTestRunProvider Class

We use the below class to perform all actions related to the Test Run objects like get all failed/passed tests, updated the test result summary and so on. It implements the IMsTestTestRunProvider interface.

Image 6

 

ProcessExecutionProvider Class

The ProcessExecutionProvider is used to start the mstest.exe with specific parameters. It implements the IProcessExecutionProvider interface.

Image 7

 

So Far in the C# Series

1. Implement Copy Paste C# Code
2. MSBuild TCP IP Logger C# Code
3. Windows Registry Read Write C# Code
4. Change .config File at Runtime C# Code
5. Generic Properties Validator C# Code
6. Reduced AutoMapper- Auto-Map Objects 180% Faster
7. 7 New Cool Features in C# 6.0
8. Types Of Code Coverage- Examples In C#
9. MSTest Rerun Failed Tests Through MSTest.exe Wrapper Application
10. Hints For Arranging Usings in Visual Studio Efficiently
11. 19 Must-Know Visual Studio Keyboard Shortcuts – Part 1
12. 19 Must-Know Visual Studio Keyboard Shortcuts – Part 2
13. Specify Assembly References Based On Build Configuration in Visual Studio
14. Top 15 Underutilized Features of .NET
15. Top 15 Underutilized Features of .NET Part 2
16. Neat Tricks for Effortlessly Format Currency in C#
17. Assert DateTime the Right Way MSTest NUnit C# Code
18. Which Works Faster- Null Coalescing Operator or GetValueOrDefault or Conditional Operator
19. Specification-based Test Design Techniques for Enhancing Unit Tests
20. Get Property Names Using Lambda Expressions in C#
21. Top 9 Windows Event Log Tips Using C#

 

If you enjoy my publications, feel free to SUBSCRIBE
Also, hit these share buttons. Thank you!

Source Code

The post- MSTest Rerun Failed Tests Through MSTest.exe Wrapper Application appeared first on Automate The Planet.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
CEO Automate The Planet
Bulgaria Bulgaria
CTO and Co-founder of Automate The Planet Ltd, inventor of BELLATRIX Test Automation Framework, author of "Design Patterns for High-Quality Automated Tests: High-Quality Test Attributes and Best Practices" in C# and Java. Nowadays, he leads a team of passionate engineers helping companies succeed with their test automation. Additionally, he consults companies and leads automated testing trainings, writes books, and gives conference talks. You can find him on LinkedIn every day.

Comments and Discussions

 
QuestionIs the source code for this sample project still available? Pin
Matthew MacFarland 202110-Jan-22 2:18
Matthew MacFarland 202110-Jan-22 2:18 
AnswerRe: Is the source code for this sample project still available? Pin
Anton Angelov16-Feb-22 21:38
Anton Angelov16-Feb-22 21:38 
To be honest, for years nobody downloaded it and with the new .NET Core I deleted the code from my GitHub. 3 years ago I created a successor to the tool - Distributed Test Runner - Meissa[^] but also I haven't time to further maintain it. Also, its idea is a bit different.
QuestionSilently does nothing if MSTestConsoleRunnerPath not set correctly Pin
Nels Olsen6-Dec-16 12:38
Nels Olsen6-Dec-16 12:38 
QuestionIntegrating nicely with msbuild? Pin
Nels Olsen6-Dec-16 6:17
Nels Olsen6-Dec-16 6:17 
Bug[My vote of 2] Counters deserialized Pin
Omer Biran7-Jun-15 1:38
Omer Biran7-Jun-15 1:38 
GeneralRe: [My vote of 2] Counters deserialized Pin
Anton Angelov7-Jun-15 1:51
Anton Angelov7-Jun-15 1:51 
QuestionThreshold issue Pin
Omer Biran6-Jun-15 21:53
Omer Biran6-Jun-15 21:53 
AnswerRe: Threshold issue Pin
Christopher Lombardi16-Feb-22 10:20
Christopher Lombardi16-Feb-22 10:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.