Gepost op 2004.04.11 |
Geen reacties |
C#
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
For this article, we're going to write a class to write to the EventLog. To allow us for easy error logging.
I'm going to use NUnit as well to test our class. You can get NUnint at http://www.nunit.org/. Install it.
Start by creating a new project called 'EventLogTests', this is a class library. Add a reference to nunit.framework.dll located in the NUnit bin directory.
Throw out the constructor and add the attribute TestFixture to the class. This will tell NUnit about a new set of tests.
[csharp]
namespace EventLogTests {
using System;
using NUnit.Framework;
using CumpsD.Tools;
using System.Diagnostics;
[TestFixture()]
public class EventLogTests {
} /* EventLogTests */
} /* EventLogTests */
[/csharp]
Now we'll add a method called Initialise, which we mark with the SetUp attribute. This method will be run at the beginning of each test. In here we will create our EventLogger object (which doesn't exist yet).
[csharp]
private EventLogger _ErrorLog;
[SetUp]
public void Initialise() {
this._ErrorLog = new EventLogger("MyCatastrophicError");
}
[/csharp]
Next thing is setting up NUnit. You can find Visual Studio add-ins for NUnit, but as I'm having some problems with getting them to work properly I'm using an alternate method. Go to the project properties, configuration properties, debugging. And set Debug Mode to Program, and Start Application to nunit-gui.exe, each time we'll press F5 NUnit will now launch.
The way of test driven development is to first write a test that fails and then add some code to make the test pass. We have already written a failing SetUp, because the EventLogger class doesn't exist yet, this counts as a failed test as well. So, let's make it pass.
Create a new class library called EventLogger and create the constructor.
[csharp]
namespace CumpsD.Tools {
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;
using System.Globalization;
public class EventLogger {
private string _ApplicationName;
private EventLog _Log;
public string ApplicationName {
get { return this._ApplicationName; }
} /* ApplicationName */
private EventLog Log {
get { return this._Log; }
} /* Log */
public EventLogger(string applicationName) {
this._ApplicationName = applicationName;
this._Log = new EventLog();
this.Log.Source = this.ApplicationName;
} /* EventLogger */
} /* EventLogger */
} /* CumpsD.Tools */
[/csharp]
Let's create our first test. We want to read an EventLog.
[csharp]
[Test]
[ExpectedException(typeof(InvalidEventLogException))]
public void ReadLog1() {
EventLogEntry[] EventLogs = this._ErrorLog.ReadLog(this._BadLog);
}
[/csharp]
We mark our method with the Test attribute, along with the ExcpectedException, because this._BadLog contains a non-existant logfile, and we want our class to throw an exception when trying that.
This test fails, because the ReadLog method doesn't exist yet. Let's create it, this requires some more coding, we'll have some private helper methods. SetLog to specify the EventLog we want to read from, and IsLog to check if the EventLog actually exists.
[csharp]
private bool IsLog(string logName) {
return EventLog.Exists(logName);
} /* IsLog */
private void SetLog(string logName) {
if (this.IsLog(logName)) {
this._Log.Log = logName;
} else {
throw new InvalidEventLogException("Invalid Logfile.");
}
} /* SetLog */
public EventLogEntry[] ReadLog(string logName) {
this.SetLog(logName);
EventLogEntry[] EventLogs = new EventLogEntry[this.Log.Entries.Count];
this.Log.Entries.CopyTo(EventLogs, 0);
return EventLogs;
} /* ReadLog */
[/csharp]
This code on it's own will still fail, because there is no InvalidEventLogException! Let's add a class in the same file defining the Exception.
[csharp]
[Serializable()]
public class InvalidEventLogException: Exception, ISerializable {
public InvalidEventLogException(): base() { }
public InvalidEventLogException(string message): base(message) { }
public InvalidEventLogException(string message, Exception innerException): base (message, innerException) { }
protected InvalidEventLogException(SerializationInfo info, StreamingContext context): base(info, context) { }
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
public new void GetObjectData(SerializationInfo info, StreamingContext context) {
base.GetObjectData(info, context);
}
} /* InvalidEventLogException */
[/csharp]
When we run our test now, the EventLogger will throw an Exception and our test will pass, because we were expecting an exception.
Write a test with a valid EventLog name as well, and make it pass, the code shown above will work.
We also want a WriteLog method, to actually log our errors, so let's make a test for that.
[csharp]
[Test]
[ExpectedException(typeof(InvalidEventLogException))]
public void WriteLog1() {
this._ErrorLog.WriteLog(this._BadLog, "This is a test entry");
}
[Test]
public void WriteLog2() {
string ErrorMessage = this._ErrorLog.WriteLog(this._GoodLog, "I have encountered a catastrophic error!");
EventLogEntry[] EventLogs = this._ErrorLog.ReadLog(this._GoodLog);
Assert.AreEqual(ErrorMessage, EventLogs[EventLogs.Length-1].Message, "Wrong Error.");
}
[/csharp]
The WriteLog1 method is similar to the ReadLog1 method, it tries to write to an invalid EventLog and will fail. The WriteLog2 method however tries to write to a valid EventLog, and checks if the error is actually written afterwards.
Both tests will fail, because we'll write the methods now.
I have created an enum for the three types of EventLogEntries, Information, Warning and Error. Along with an overload for WriteLog so it would write an Error by default.
[csharp]
public enum ErrorType { Information, Warning, Error }
public string WriteLog(string logName, string errorMessage) {
return this.WriteLog(logName, errorMessage, ErrorType.Error);
} /* WriteLog */
[/csharp]
Our real WriteLog method will check for a valid EventLog and then write the Entry to the EventLog and return the error message it has written, so we can compare in our test.
[csharp]
public string WriteLog(string logName, string errorMessage, ErrorType errorType) {
this.SetLog(logName);
EventLogEntryType LogType;
switch (errorType) {
case ErrorType.Information: LogType = EventLogEntryType.Information; break;
case ErrorType.Warning: LogType = EventLogEntryType.Warning; break;
case ErrorType.Error: LogType = EventLogEntryType.Error; break;
default: LogType = EventLogEntryType.Error; break;
}
this.Log.WriteEntry(String.Format(CultureInfo.InvariantCulture, "{0} caused the following error:\n{1}", this.ApplicationName, errorMessage), LogType);
return String.Format(CultureInfo.InvariantCulture, "{0} caused the following error:\n{1}", this.ApplicationName, errorMessage);
} /* WriteLog */
[/csharp]
If we run our tests now, we'll see they succeed. I have added some more tests to check if the Entry type was written correctly.
At the end you should have something like this:
And when you check eventvwr.msc you will see something like:
As always, I've uploaded the sources so you can check them on your own.
I'm going to use NUnit as well to test our class. You can get NUnint at http://www.nunit.org/. Install it.
Start by creating a new project called 'EventLogTests', this is a class library. Add a reference to nunit.framework.dll located in the NUnit bin directory.
Throw out the constructor and add the attribute TestFixture to the class. This will tell NUnit about a new set of tests.
[csharp]
namespace EventLogTests {
using System;
using NUnit.Framework;
using CumpsD.Tools;
using System.Diagnostics;
[TestFixture()]
public class EventLogTests {
} /* EventLogTests */
} /* EventLogTests */
[/csharp]
Now we'll add a method called Initialise, which we mark with the SetUp attribute. This method will be run at the beginning of each test. In here we will create our EventLogger object (which doesn't exist yet).
[csharp]
private EventLogger _ErrorLog;
[SetUp]
public void Initialise() {
this._ErrorLog = new EventLogger("MyCatastrophicError");
}
[/csharp]
Next thing is setting up NUnit. You can find Visual Studio add-ins for NUnit, but as I'm having some problems with getting them to work properly I'm using an alternate method. Go to the project properties, configuration properties, debugging. And set Debug Mode to Program, and Start Application to nunit-gui.exe, each time we'll press F5 NUnit will now launch.
The way of test driven development is to first write a test that fails and then add some code to make the test pass. We have already written a failing SetUp, because the EventLogger class doesn't exist yet, this counts as a failed test as well. So, let's make it pass.
Create a new class library called EventLogger and create the constructor.
[csharp]
namespace CumpsD.Tools {
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;
using System.Globalization;
public class EventLogger {
private string _ApplicationName;
private EventLog _Log;
public string ApplicationName {
get { return this._ApplicationName; }
} /* ApplicationName */
private EventLog Log {
get { return this._Log; }
} /* Log */
public EventLogger(string applicationName) {
this._ApplicationName = applicationName;
this._Log = new EventLog();
this.Log.Source = this.ApplicationName;
} /* EventLogger */
} /* EventLogger */
} /* CumpsD.Tools */
[/csharp]
Let's create our first test. We want to read an EventLog.
[csharp]
[Test]
[ExpectedException(typeof(InvalidEventLogException))]
public void ReadLog1() {
EventLogEntry[] EventLogs = this._ErrorLog.ReadLog(this._BadLog);
}
[/csharp]
We mark our method with the Test attribute, along with the ExcpectedException, because this._BadLog contains a non-existant logfile, and we want our class to throw an exception when trying that.
This test fails, because the ReadLog method doesn't exist yet. Let's create it, this requires some more coding, we'll have some private helper methods. SetLog to specify the EventLog we want to read from, and IsLog to check if the EventLog actually exists.
[csharp]
private bool IsLog(string logName) {
return EventLog.Exists(logName);
} /* IsLog */
private void SetLog(string logName) {
if (this.IsLog(logName)) {
this._Log.Log = logName;
} else {
throw new InvalidEventLogException("Invalid Logfile.");
}
} /* SetLog */
public EventLogEntry[] ReadLog(string logName) {
this.SetLog(logName);
EventLogEntry[] EventLogs = new EventLogEntry[this.Log.Entries.Count];
this.Log.Entries.CopyTo(EventLogs, 0);
return EventLogs;
} /* ReadLog */
[/csharp]
This code on it's own will still fail, because there is no InvalidEventLogException! Let's add a class in the same file defining the Exception.
[csharp]
[Serializable()]
public class InvalidEventLogException: Exception, ISerializable {
public InvalidEventLogException(): base() { }
public InvalidEventLogException(string message): base(message) { }
public InvalidEventLogException(string message, Exception innerException): base (message, innerException) { }
protected InvalidEventLogException(SerializationInfo info, StreamingContext context): base(info, context) { }
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
public new void GetObjectData(SerializationInfo info, StreamingContext context) {
base.GetObjectData(info, context);
}
} /* InvalidEventLogException */
[/csharp]
When we run our test now, the EventLogger will throw an Exception and our test will pass, because we were expecting an exception.
Write a test with a valid EventLog name as well, and make it pass, the code shown above will work.
We also want a WriteLog method, to actually log our errors, so let's make a test for that.
[csharp]
[Test]
[ExpectedException(typeof(InvalidEventLogException))]
public void WriteLog1() {
this._ErrorLog.WriteLog(this._BadLog, "This is a test entry");
}
[Test]
public void WriteLog2() {
string ErrorMessage = this._ErrorLog.WriteLog(this._GoodLog, "I have encountered a catastrophic error!");
EventLogEntry[] EventLogs = this._ErrorLog.ReadLog(this._GoodLog);
Assert.AreEqual(ErrorMessage, EventLogs[EventLogs.Length-1].Message, "Wrong Error.");
}
[/csharp]
The WriteLog1 method is similar to the ReadLog1 method, it tries to write to an invalid EventLog and will fail. The WriteLog2 method however tries to write to a valid EventLog, and checks if the error is actually written afterwards.
Both tests will fail, because we'll write the methods now.
I have created an enum for the three types of EventLogEntries, Information, Warning and Error. Along with an overload for WriteLog so it would write an Error by default.
[csharp]
public enum ErrorType { Information, Warning, Error }
public string WriteLog(string logName, string errorMessage) {
return this.WriteLog(logName, errorMessage, ErrorType.Error);
} /* WriteLog */
[/csharp]
Our real WriteLog method will check for a valid EventLog and then write the Entry to the EventLog and return the error message it has written, so we can compare in our test.
[csharp]
public string WriteLog(string logName, string errorMessage, ErrorType errorType) {
this.SetLog(logName);
EventLogEntryType LogType;
switch (errorType) {
case ErrorType.Information: LogType = EventLogEntryType.Information; break;
case ErrorType.Warning: LogType = EventLogEntryType.Warning; break;
case ErrorType.Error: LogType = EventLogEntryType.Error; break;
default: LogType = EventLogEntryType.Error; break;
}
this.Log.WriteEntry(String.Format(CultureInfo.InvariantCulture, "{0} caused the following error:\n{1}", this.ApplicationName, errorMessage), LogType);
return String.Format(CultureInfo.InvariantCulture, "{0} caused the following error:\n{1}", this.ApplicationName, errorMessage);
} /* WriteLog */
[/csharp]
If we run our tests now, we'll see they succeed. I have added some more tests to check if the Entry type was written correctly.
At the end you should have something like this:
And when you check eventvwr.msc you will see something like:
As always, I've uploaded the sources so you can check them on your own.