Работая с фреймворком для unit-тестирования .NET сборок NUnit, Вы не можете получить доступ к событиям тестирования из Ваших фикстур (Fixture) напрямую. Для отслеживания событий Вы должны написать add-in. В этой статье я приведу простой пример того, как это можно сделать.
Создадим простую библиотеку классов, которую будем тестировать.

using System;

namespace TestedLibrary {
public class Engine {
uint cylinderCount = 1;
float capacity = 0.0f;
float power = 0.0f;

public uint CylinderCount {
get {
return cylinderCount;
}

set {
if(value < 1) {
throw new Exception("Cylinder count must be great then 0");
}
cylinderCount = value;
}
}

public float Capacity {
get {
return capacity;
}

set {
if(value < 0.0) {
throw new Exception("Engine capacity must be great then 0.0");
}
capacity = value;
}
}

public float Power {
get {
return power;
}

set {
if(value < 0.0) {
throw new Exception("Engine power must be great then 0.0");
}
power = value;
}
}
}
}
namespace TestedLibrary {
public class Car {
public Car(string name, Engine engine) {
Name = name;
Engine = engine;
}

public string Name { get; set; }

public Engine Engine { get; set; }
}
}

Создадим проект для тестирования и добавим в него пару фикстур:

using NUnit.Framework;
using TestedLibrary;

namespace Testing {
[TestFixture]
public class EngineTest {
[Test]
public void DefaultValues() {
Engine engine = new Engine();
Assert.AreEqual(0.0f, engine.Power, 0.1f);
Assert.AreEqual(0.0f, engine.Capacity, 0.1f);
Assert.AreEqual(1u, engine.CylinderCount);
}

[Test]
public void ManuallySettedValues() {
Engine engine = new Engine() {
Capacity = 1995.0f,
CylinderCount = 4u,
Power = 135000.0f
};
Assert.AreEqual(135000.0f, engine.Power, 0.1f);
Assert.AreEqual(1995.0f, engine.Capacity, 0.1f);
Assert.AreEqual(4u, engine.CylinderCount);
}
}
}
using NUnit.Framework;
using TestedLibrary;

namespace Testing {
[TestFixture]
public class CarTest {
[Test]
public void Creation() {
Engine engine = new Engine() {
Capacity = 1995.0f,
CylinderCount = 4u,
Power = 135000.0f
};
Car car = new Car("BMW Х3 xDrive20d Urban", engine);
Assert.AreSame(engine, car.Engine);
Assert.AreEqual("BMW Х3 xDrive20d Urban", car.Name);
}
}
}

Теперь можем скомпилировать и запустить unit-тесты для нашей библиотеки

Как я уже говорил, чтобы добраться до событий тестирования, нужно создать add-in для NUnit. Для этого нужно создать новый проект C# из шаблона «Class Library» и добавить в него ссылки на сборки nunit.core.dll и nunit.core.interfaces.dll. По умолчанию они находятся в каталоге C:Program Files (x86)NUnit 2.5.10binnet-2.0lib.
При запуске NUnit просматривает каталог addins, находящийся рядом с исполняемым файлом, и загружает из всех сборок классы с атрибутом NUnit.Core.Extensibility.NUnitAddinAttribute. Кроме того, эти классы должны реализовывать интерфейс NUnit.Core.Extensibility.IAddin, содержащий всего один метод:

namespace NUnit.Core.Extensibility {
public interface IAddin {
bool Install(IExtensionHost host);
}
}

Для того, чтобы подписаться на прослушивание событий, мы должны реализовать интерфейс NUnit.Core.EventListener и передать объект этого интерфейса в метод Install объекта типа NUnit.Core.Extensibility.IExtensionPoint, который нужно получить следующим образом:

IExtensionPoint listeners = host.GetExtensionPoint("EventListeners");

Наш класс прослушивания будет называться NUnitReporter и его целью будет создание отчёта в формате HTML. В качестве параметра конструктор этого класса будет принемать имя выходного файл. С учётом всего выше сказанного, полный код класса-расширения будет следующим:

using NUnit.Core.Extensibility;

namespace NUnitReport {
[NUnitAddin]
public class Addin : IAddin {
public bool Install(IExtensionHost host) {
IExtensionPoint listeners = host.GetExtensionPoint("EventListeners");
listeners.Install(new NUnitReporter(@"D:report.html"));
return true;
}
}
}

Интерфейс NUnit.Core.EventListener выглядит следующим образом:

namespace NUnit.Core {
public interface EventListener {
void RunFinished(Exception exception);
void RunFinished(TestResult result);
void RunStarted(string name, int testCount);
void SuiteFinished(TestResult result);
void SuiteStarted(TestName testName);
void TestFinished(TestResult result);
void TestOutput(TestOutput testOutput);
void TestStarted(TestName testName);
void UnhandledException(Exception exception);
}
}

Методы RunStarted и RunFinished вызываются при старте и завершении тестирования. В качестве аргумента методу RunFinished передаются результаты всего тестирования или объект возникшего исключения. SuiteStarted и SuiteFinished вызываются для всех сущностей шире единичного теста: класс, пространство имён, Run. Методы TestStarted и TestFinished будут вызваны при запуске и завершении каждого теста. Наконец, метод UnhendledException вызывается, как ясно из названия, при возникновении необработанного исключения.
Приведу простейшую реализацию класса NUnitReporter, собирающего информацию в файл HTML.

using System;
using System.Collections;
using System.IO;
using System.Text;
using NUnit.Core;

namespace NUnitReport {
public class NUnitReporter : EventListener {
string outFileName;
StringBuilder html;

public NUnitReporter(string outFileName) {
this.outFileName = outFileName;
}

public void RunFinished(System.Exception exception) {
}

public void RunFinished(TestResult result) {
PrintRunResult(result);
using(FileStream stream = File.OpenWrite(outFileName)) {
byte[] content = Encoding.UTF8.GetBytes(html.ToString());
stream.Write(content, 0, content.Length);
}
}

void PrintRunResult(TestResult result) {
html = new StringBuilder(
"<html>rn" +
"<head>rn" +
"<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>rn" +
"<title>NUnit Report</title>rn" +
"</head>rn" +
"<body>rn" +
"<div id='run'>rn");
html.AppendLine(String.Format("<h1>{0}</h1>", result.Name));
PrintSuiteResults(result.Results);
html.AppendLine(
"</div>rn" +
"</body>rn" +
"</html>");
}

void PrintSuiteResults(IList results) {
foreach(TestResult result in results) {
html.AppendLine("<div class='suite'>");
html.AppendLine(String.Format("<h2>{0}</h2>", result.Name));
PrintClassResults(result.Results);
html.AppendLine("</div>");
}
}

void PrintClassResults(IList results) {
foreach(TestResult result in results) {
html.AppendLine("<div class='class'>");
html.AppendLine(String.Format("<h3>{0}</h3>", result.Name));
PrintTestResults(result.Results);
html.AppendLine("</div>");
}
}

void PrintTestResults(IList results) {
foreach(TestResult result in results) {
string stringResult = "";
if(result.IsSuccess) stringResult = "Success";
else if(result.IsFailure) stringResult = "Failure";
else if(result.IsError) stringResult = "Error";
html.AppendLine("<div class='test'>");
html.AppendLine(String.Format("<p>{0}: {1}</p>", result.Name, stringResult));
html.AppendLine("</div>");
}
}

public void RunStarted(string name, int testCount) {
}

public void SuiteFinished(TestResult result) {
}

public void SuiteStarted(TestName testName) {
}

public void TestFinished(TestResult result) {
}

public void TestOutput(TestOutput testOutput) {
}

public void TestStarted(TestName testName) {
}

public void UnhandledException(System.Exception exception) {
}
}
}

Больший интерес во всём примере представляет объект класса NUnit.Core.TestResult. Этот объект содержит информацию о результатах только что завершившегося теста и всех вложенных. К примеру, в результат тестирования пространства имён будут вложены результаты тестирования всех его классов. Эти результаты помещаются в список NUnit.Core.TestResult.Results.
Кроме того, класс NUnit.Core.TestResult имеет множетсво интересных свойств:

public int AssertCount { get; set; }
public string Description { get; }
public bool Executed { get; }
public FailureSite FailureSite { get; }
public virtual string FullName { get; }
public bool HasResults { get; }
public virtual bool IsError { get; }
public virtual bool IsFailure { get; }
public virtual bool IsSuccess { get; }
public string Message { get; }
public virtual string Name { get; }
public IList Results { get; }
public ResultState ResultState { get; }
public virtual string StackTrace { get; set; }
public ITest Test { get; }
public double Time { get; set; }

Файл, содержащий класс Addin следует положить в каталог addins, рядом с исполняемым файлом nunit.exe. Если такого каталога не существует, его нужно создать. Для отладки библиотеки следует аттачиться к процессу nunit-agent.exe (Debug->Attach to Process).
Полученный отчёт будет выгладить так:

Естественно, что приведённый пример сильно упрощён и не годится для реального использования, но, я надеюсь, демонстрирует общий подход.
Исходные тексты проекта можно скачать здесь.