Reputation: 18186
I have an ASP.NET MVC web app used to monitor the state of my company's system - windows services, wcf services, web apps, etc... I have a C# NUnit test project that has test cases that check different things within the system. Currently I use TestDriven.Net to run the test cases within Visual Studio, or I can run the test cases using the nunit gui.
What I would like to do is import the test project dll into my ASP.NET MVC web app and somehow invoke it to run all test cases within the project and output the test case name, duration, and result of each test case to a web page. Is this possible? If so, how would I go about doing it or is there some pre-built framework that gives this ability? I know that CI frameworks like teamCity can run the test cases and output to a webpage, but I'm looking for it to be within my own web app and for me to have some level of control over how the output is designed.
Upvotes: 1
Views: 1501
Reputation: 18186
I first tried starting the nunit-console process using Process.Start and redirecting its output to my web app, however this is not a good solution because the redirected output from the Process class is buffered and you have no control over when it flushes the buffer. I found certain programs like msbuild work great and it flushes constantly, however with nunit-console it holds onto the output until all test cases are complete, which means you can't see the progress of the test cases as they run.
The solution is to use the RemoteTestRunner nunit class and create an event listener class that implements the NUnit.Core.EventListener interface:
public class NUnitEventListener : NUnit.Core.EventListener
{
public event EventHandler CompletedRun;
public StringBuilder Output;
private int TotalTestsPassed = 0;
private int TotalTestsErrored = 0;
public void RunStarted(string name, int testCount)
{
Output.AppendLine(TimeStamp + "Running " + testCount + " tests in " + name + "<br/><br/>");
TotalTestsPassed = 0;
TotalTestsErrored = 0;
}
public void RunFinished(System.Exception exception)
{
Output.AppendLine(TimeStamp + "Run errored: " + exception.ToString() + "<br/>");
//notify event consumers.
if (CompletedRun != null)
CompletedRun(exception, new EventArgs());
}
public void RunFinished(TestResult result)
{
Output.AppendLine(TimeStamp + "<label class='normal " + (TotalTestsErrored == 0 ? "green" : "red")
+ "'>" + TotalTestsPassed + " tests passed, " + TotalTestsErrored + " tests failed</label><br/>");
Output.AppendLine(TimeStamp + "Run completed in " + result.Time + " seconds<br/>");
//notify event consumers.
if (CompletedRun != null)
CompletedRun(result, new EventArgs());
}
public void TestStarted(TestName testName)
{
Output.AppendLine(TimeStamp + testName.FullName + "<br/>");
}
public void TestOutput(TestOutput testOutput)
{
if(testOutput.Text.IndexOf("NHibernate:") == -1)
Output.AppendLine(TimeStamp + testOutput.Text + "<br/>");
}
public void TestFinished(TestResult result)
{
if (result.IsSuccess)
{
Output.AppendLine(TimeStamp + "<label class='green normal'>Test Passed!</label><br/><br/>");
TotalTestsPassed++;
}
else
{
Output.AppendLine(TimeStamp + "<label class='red normal'>Test Failed!<br/>" + result.Message.Replace(Environment.NewLine, "<br/>") + "</label><br/>");
TotalTestsErrored++;
}
}
public void UnhandledException(System.Exception exception)
{
Output.AppendLine(TimeStamp + "Unhandled Exception: " + exception.ToString() + "<br/>");
}
public void SuiteStarted(TestName testName)
{
}
public void SuiteFinished(TestResult result)
{
}
private string TimeStamp
{
get
{
return "[" + DateTime.Now.ToString() + "] ";
}
}
}
After that, create a TestRunner class that calls RemoteTestRunner and uses your sexy new event listener class:
public static class TestRunner
{
public static bool InProgress = false;
public static StringBuilder Output = new StringBuilder();
private static RemoteTestRunner Runner;
public static void Start(string fileName)
{
InProgress = true;
Output = new StringBuilder();
StartTests(fileName);
}
private static void StartTests(string fileName)
{
//start nunit.
var testPackage = new TestPackage(fileName);
Runner = new RemoteTestRunner();
Runner.Load(testPackage);
var nunitEventListener = new NUnitEventListener();
nunitEventListener.CompletedRun += new EventHandler(nunitEventListener_CompletedRun);
nunitEventListener.Output = Output;
Runner.BeginRun(nunitEventListener);
}
static void nunitEventListener_CompletedRun(object sender, EventArgs e)
{
if (Runner != null)
{
Runner.CancelRun();
Runner = null;
}
InProgress = false;
}
}
Now call the TestRunner class in your ASP.NET MVC Controller:
public class TestController : ApplicationController
{
//GET: /Test/Index/
public ActionResult Index()
{
TestRunner.Start(@"C:\PathToTestProject\bin\Release\SystemTest.dll");
return View();
}
//POST: /Test/GetOutput/
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetOutput()
{
var result = new
{
InProgress = TestRunner.InProgress,
Output = TestRunner.Output.ToString()
};
return Json(result);
}
}
Lastly, create a simple view to show the output as the test cases run. Mine uses dojo but it can easily be modified to work with jquery or vanilla javascript:
<script type="test/javascript">
var nunit = {
init: function () {
nunit.get();
},
get: function () {
//ajax call.
ajax.post("test/getoutput/", {}, nunit.display);
},
display: function (result) {
console.debug(result);
dojo.byId("output").innerHTML = result.Output.length > 0 ? result.Output : dojo.byId("output").innerHTML;
if (result.InProgress)
window.setTimeout(nunit.get, 10000);
}
};
dojo.addOnLoad(nunit.init);
</script>
<div id="output">
The tests are running, please wait....
</div>
That's it... Hope this helps some others, as all of the examples online of RemoteTestRunner (including on stackoverflow) pass in a NullListener, which means you can't capture the output of the test run.
Upvotes: 4
Reputation: 20643
I wonder if you know about CruiseControl.NET that is a open source project for CI. If not, Check its web app for build report manager http://sourceforge.net/projects/ccnet/ you will be able to see how they achieved what you want to do.
Upvotes: 1