Command line test runner for VS 2012 comes with a parameter /logger where user can specify logger to be used. Two loggers shipped with VS are Console and Trx. Console logger is default logger that prints output of test execution to console window. Trx logger is can be used to generate trx file for test run (/logger:trx).

Here I am describing how to write a custom logger that can be used with vstest.console.exe

There are two requirements for custom test logger

1. Implement interface Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestLogger

Interface can be found in “C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll”

2. Assembly containing logger implementation to be present in place where extensions are searched like "C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions"

Assembly can be installed with VSIX like test adapters. In that case use /UseVSIXExtensions parameter also.

 

ITestLogger has only one method

/// <summary>
/// Initializes the Test Logger.
/// </summary>
/// <param name="events">Events that can be registered for.</param>
/// <param name="testRunDirectory">Test Run Directory</param>
void Initialize(TestLoggerEvents events, string testRunDirectory);

 

This function is used to hookup to following events in TestLoggerEvents

/// <summary>
/// Raised when a test message is received.
/// </summary>
public abstract event EventHandler<TestRunMessageEventArgs> TestRunMessage;
 
/// <summary>
/// Raised when a test result is received.
/// </summary>
public abstract event EventHandler<TestResultEventArgs> TestResult;
 
/// <summary>
/// Raised when a test run is complete.
/// </summary>
public abstract event EventHandler<TestRunCompleteEventArgs> TestRunComplete;

 

For Ex:

/// <summary>
/// Initializes the Test Logger.
/// </summary>
/// <param name="events">Events that can be registered for.</param>
/// <param name="testRunDirectory">Test Run Directory</param>
public void Initialize(TestLoggerEvents events, string testRunDirectory)
{
    // Register for the events.
    events.TestRunMessage += TestMessageHandler;
    events.TestResult += TestResultHandler;
    events.TestRunComplete += TestRunCompleteHandler;
}

 

Each Event handler now can be used to log event however you want (in file, db, console, etc). Following example prints in on console:

/// <summary>
/// Logger for sending output to the console.
/// </summary>
[ExtensionUri("logger://SimpleConsoleLogger/v1")] /// Uri used to uniquely identify the console logger. 
[FriendlyName("SimpleLogger")] /// Alternate user friendly string to uniquely identify the logger.
internal class SimpleConsoleLogger : ITestLogger
{
    /// <summary>
    /// Initializes the Test Logger.
    /// </summary>
    /// <param name="events">Events that can be registered for.</param>
    /// <param name="testRunDirectory">Test Run Directory</param>
    public void Initialize(TestLoggerEvents events, string testRunDirectory)
    {
        // Register for the events.
        events.TestRunMessage += TestMessageHandler;
        events.TestResult += TestResultHandler;
        events.TestRunComplete += TestRunCompleteHandler;
    }
 
    /// <summary>
    /// Called when a test message is received.
    /// </summary>
    private void TestMessageHandler(object sender, TestRunMessageEventArgs e)
    {
        switch (e.Level)
        {
            case TestMessageLevel.Informational:
                Console.WriteLine("Information: " + e.Message);
                break;
 
            case TestMessageLevel.Warning:
                Console.WriteLine("Warning: " + e.Message);
                break;
 
            case TestMessageLevel.Error:
                Console.WriteLine("Error: " + e.Message);
                break;
 
            default:
                break;
        }
    }
 
    /// <summary>
    /// Called when a test result is received.
    /// </summary>
    private void TestResultHandler(object sender, TestResultEventArgs e)
    {
        string name = !string.IsNullOrEmpty(e.Result.DisplayName) ? e.Result.DisplayName : e.Result.TestCase.FullyQualifiedName;
 
        if (e.Result.Outcome == TestOutcome.Skipped)
        {
            Console.WriteLine(name + " Skipped");
        }
        else if (e.Result.Outcome == TestOutcome.Failed)
        {    
            Console.WriteLine(name + " Failed");
            if(!String.IsNullOrEmpty(e.Result.ErrorStackTrace))
            {
                Console.WriteLine(e.Result.ErrorStackTrace);                    
            }
        }
        else if (e.Result.Outcome == TestOutcome.Passed)
        {
            Console.WriteLine(name + " Passed");
        }
    }
    
    /// <summary>
    /// Called when a test run is completed.
    /// </summary>
    private void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e)
    {
        Console.WriteLine("Total Executed: {0}", e.TestRunStatistics.ExecutedTests);
        Console.WriteLine("Total Passed: {0}", e.TestRunStatistics[TestOutcome.Passed]);
        Console.WriteLine("Total Failed: {0}", e.TestRunStatistics[TestOutcome.Failed]);
        Console.WriteLine("Total Skipped: {0}", e.TestRunStatistics[TestOutcome.Skipped]);
    }
}

 

Once binary is placed at proper location, above logger can be used as

vstest.console.exe <testdll> /logger:SimpleLogger

 

If you've any feedback or issue then please do send us on Connect or in the Forums.