I just started test开发者_开发技巧ing xUnit.net, but it doesn't seem to capture any output (Console, Debug, Trace), as I would have expected.
Is that possible? I am using a sample .NET 4.0 class-library with xUnit.net 1.8.
The situation has changed a little with xUnit.net 2. I know the question is about an earlier version, but as people will land here having performed the upgrade, I thought it was worth pointing this out.
In order to see some kind of output in the test output in version 2 you will need to take a dependency in your test class (via a constructor argument) on an instance of Xunit.Abstractions.ITestOutputHelper
, then use the WriteLine
method on this interface. E.g.:
public class MyTestSpec
{
private readonly ITestOutputHelper _testOutputHelper;
public MyTestSpec(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
}
[Fact]
public void MyFact()
{
_testOutputHelper.WriteLine("Hello world");
}
}
You could choose to hook up your logging framework to this interface, perhaps by injecting an ILog
implementation that forwarded all calls to ITestOutpuHelper
.
I acknowledge that you won't want to do this by default, but for diagnostic purposes from time to time it can be quite useful. This is especially true where your tests only fail on some cloud based build & test server!
This can help if your Console.Write
is embedded deep down some class hierarchy that you don't want to refactor:
public MyTestClass(ITestOutputHelper output)
{
var converter = new Converter(output);
Console.SetOut(converter);
}
private class Converter : TextWriter
{
ITestOutputHelper _output;
public Converter(ITestOutputHelper output)
{
_output = output;
}
public override Encoding Encoding
{
get { return Encoding.Whatever; }
}
public override void WriteLine(string message)
{
_output.WriteLine(message);
}
public override void WriteLine(string format, params object[] args)
{
_output.WriteLine(format, args);
}
public override void Write(char value)
{
throw new NotSupportedException("This text writer only supports WriteLine(string) and WriteLine(string, params object[]).");
}
}
I used Console.SetOut to output Console.Writes to .NET Test Log (in Visual Studio Code).
using System;
using System.IO;
using Xunit;
using Xunit.Abstractions;
namespace UnitTest
{
public class TestClass
{
private ITestOutputHelper output;
public TestClass(ITestOutputHelper output)
{
this.output = output;
}
public class ConsoleWriter : StringWriter
{
private ITestOutputHelper output;
public ConsoleWriter(ITestOutputHelper output)
{
this.output = output;
}
public override void WriteLine(string m)
{
output.WriteLine(m);
}
}
[Fact]
public void TestName()
{
Console.SetOut(new ConsoleWriter(output));
Assert.True(ToBeTested.Foo());
}
public class ToBeTested
{
public static bool Foo()
{
Console.WriteLine("Foo uses Console.WriteLine!!!");
return true;
}
}
}
}
But it is easier to just run the test via console
dotnet test
There the output will be shown without any modifications of test class.
It is different since the .NET Test Log-window uses the TRX-format (Visual Studio Test Results File), see
dotnet test -h | grep logger
For more information about TRX.
There is a solution as found here: https://xunit.codeplex.com/discussions/211566
Simply add this to your constructor or method where you want debugging output:
Debug.Listeners.Add(new DefaultTraceListener());
This was a simple solution I've done using a StringBuilder to capture the output and only output it in case of test failure:
[Fact]
public void UnitTest1()
{
var sb = new StringBuilder();
try
{
// ... the test code ...
sb.AppendLine("Put your debug information here.");
int expected = 1;
int actual = 2;
// What I really want to check:
Assert.Equal(expected, actual);
}
// Catch exceptions from the Assert
catch (Exception e)
{
sb.AppendLine("The original failure:");
sb.AppendLine(e.Message);
sb.AppendLine(e.StackTrace);
Assert.True(false, sb.ToString());
}
}
Since only the Xunit Assert.True() method takes a message, I use it in the catch and provide the "log" information via its message, which you will see if the test fails.
You can get rid of the try/catch if you only use Assert.True() in the test and provide sb.ToString() as the message.
I landed here with the same question. Here's what I ended up with. I hope it helps someone else.
How to write a custom target
/// <summary>
/// Use this to output NLog information when running test cases.
/// </summary>
[Target("XUnit")]
public class XUnitTarget : TargetWithLayout
{
[RequiredParameter] public ITestOutputHelper OutputHelper { get; set; }
protected override void Write(LogEventInfo logEvent)
{
var logMessage = Layout.Render(logEvent);
OutputHelper.WriteLine(logMessage);
}
/// <summary>
/// Call this in the test where you wish to enable logging.
/// </summary>
/// <param name="testOutputHelper">The xUnit output helper from the test.</param>
public static void Configure(ITestOutputHelper testOutputHelper)
{
var config = new LoggingConfiguration();
var xUnitTarget = new XUnitTarget
{
OutputHelper = testOutputHelper
};
config.AddTarget("xUnit", xUnitTarget);
config.AddRule(LogLevel.Trace, LogLevel.Fatal, xUnitTarget);
LogManager.Configuration = config;
}
}
The only method you'll need to override is Write(char[] buffer, int index, int count). All other methods will end up using this one.
public class TestOutputWriter : TextWriter
{
private readonly ITestOutputHelper _output;
public TestOutputWriter(ITestOutputHelper output)
{
_output = output;
}
public override Encoding Encoding => Encoding.UTF8;
public override void Write(char[] buffer, int index, int count)
{
_output.WriteLine(new string(buffer, index, count));
}
}
Now you can do:
Console.SetOut(new TestOutputHelper(outputHelper));
The only thing you'd might want to do is some 'magic' buffering when the character buffer doesn't contain a new line ('\n') as ITestOutputHelper only contains a WriteLine method.
In general, it's a bad road to go down to be reliant on logging and tests. The pass/fail should be the outcome of the tests. And they simply shouldn't get to the stage where there's enough stuff going on that looking at a trace will be necessary.
The xunit.gui.exe
shows Console and Trace output, xunit.console.exe
does not. If it's important, you could hook up a TraceListener which redirects to a file by making appropriate standard .NET config entries (Theres' a FileWriterTraceListener
which you should be able to hook in if you google it).
UPDATE: As discussed in his blog post, Damian Hickey has a good example of a possible substitute - wiring logging to the xUnit 2 ITestOutputHelper
as demonstrated in https://github.com/damianh/CapturingLogOutputWithXunit2AndParallelTests/blob/master/src/Lib.Tests/Tests.cs
UPDATE 2: In some cases, one can add logging and feed it to the ITestOutputHelper
without involving LogContext
by using a simple adapter as follows (I only have it in F#, sorry):
// Requirement: Make SUT depend on Serilog NuGet
// Requirement: Make Tests depend on Serilog.Sinks.Observable
type TestOutputAdapter(testOutput : Xunit.Abstractions.ITestOutputHelper) =
let formatter = Serilog.Formatting.Display.MessageTemplateTextFormatter(
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}", null);
let write logEvent =
use writer = new System.IO.StringWriter()
formatter.Format(logEvent, writer);
writer |> string |> testOutput.WriteLine
member __.Subscribe(source: IObservable<Serilog.Events.LogEvent>) =
source.Subscribe write
let createLogger hookObservers =
LoggerConfiguration()
.WriteTo.Observers(Action<_> hookObservers)
.CreateLogger()
let createTestOutputLogger (output: ITestOutputHelper) =
let adapter = TestOutputAdapter testOutputHelper
createLogger (adapter.Subscribe >> ignore)
type Tests(testOutputHelper) =
let log = createTestOutputLogger testOutputHelper
[<Fact>] let feedToSut () =
// TODO pass log to System Under Test either as a ctor arg or a method arg
The difference with this approach vs using the log context is that logging to the global [contextualized] Serilog Logger
will not get picked up.
精彩评论