• entries
146
435
• views
198844

# On .Net Serialization, Part 4

577 views

On testing and refactoring: I've always been a strong believer in TDD and refactoring. Thus you will often find that my code will change appearance as it grows. You will also find that the number of tests that I perform will also grow rather quickly. Quite simply, testing and refactoring keeps things working and bugs away. Not only that but tests are a nice verifiable form of documentation. The testing framework I have been using is NUnit. It's a great test framework, it is clean and simple to use, a cinch to get up and running, and supports most versions of the .Net framework right out of the box. It's also easy to script into your build process by using the command line test runner. As a note on refactoring: I will attempt to always indicate refactorings of my code using bold. However, this may not always happen, depending on the circumstances.

Well, lets begin building our NetworkFormatter, as I've opted to call it. The first thing to do is to write a test case for it's usage:
Quote:
 namespace Kent.Serialization.Network.Tests { [TestFixture] public class NetworkFormatterTests { [Test] public void CreateFormatter() { NetworkFormatter formatter = new NetworkFormatter(); Assert.IsNotNull(formatter); } }}

Seems simple enough, however if you compile the code you will get an error (as you probably already know). The error message will indicate that we have yet to write a NetworkFormatter class, and hence it has no idea what our code is talking about. Adding the simplest case, a blank class with the name NetworkFormatter will not only let this code compile, but also will pass the test, so lets do that now:
Quote:
 namespace Kent.Serialization.Network { public class NetworkFormatter { }}

The next step should be serializing some data, so writing another simple test for that, we get
Quote:
 [Test]public void TestSerialization() { MemoryStream testStream = new MemoryStream(); NetworkFormatter formatter = new NetworkFormatter(); formatter.Serialize(testStream, new Object());}

So, now we need to make the test pass, simply adding a blank method will do.
Quote:
 public void Serialize(Stream destinationStream, object graph) {}

Running the tests, we find that it fails with the message: "Kent.Serialization.Network.Tests.NetworkFormatterTests.TestSerialization : No data was written to the stream." Of course, the reason is simple: we never wrote anything to the stream in the first place. So, what do we need to do to get this test to pass? Simple, write some data to the stream. However, just writing any data to the stream won't be particularily helpful, instead we should write something meaningful. So what should we write? Well, we can start by serializing one of our classes. Since all of our serializable data is stored in the shared assembly, we can simply reference it and serialize it. However, if we are going to be using an ID scheme to designate types, we also need to somehow assign the types in our shared assembly unique IDs. The simplest way of doing this is to pass the shared assembly to the formatter, and let it assign the IDs. Passing the assembly every serialize and deserialize call is rather silly, though, since we already know that there will be only a single shared assembly. So, lets pass it in to the constructor:
Quote:
 public NetworkFormatter(Assembly sharedAssembly) { this.sharedAssembly = sharedAssembly;}private Assembly sharedAssembly;

Of course, compiling this code breaks our two tests, so fixing them, and doing a bit of refactoring we get:
Quote:
 [TestFixture]public class NetworkFormatterTests { [Test] public void CreateFormatter() { NetworkFormatter formatter = GetFormatter(); } [Test] public void TestSerialization() { MemoryStream testStream = new MemoryStream(); NetworkFormatter formatter = GetFormatter(); formatter.Serialize(testStream, new Object()); Assert.IsTrue(testStream.Length > 0, "No data was written to the stream."); } private NetworkFormatter GetFormatter() { System.Reflection.Assembly sharedAssembly = typeof(Kent.Shared.Vector).Assembly; NetworkFormatter formatter = new NetworkFormatter(sharedAssembly); Assert.IsNotNull(formatter); return formatter; }}

Now, if at this point you are wondering why I'm going through all of the effort of writing tests, and keeping them up to date, not to mention, we haven't really written anything that DOES anything yet, then you should stop and consider this: If you write the whole serializer right now, and then compile it, you will most likely end up with a nice load of errors, and when you have those errors fixed, you will probably have plenty of bugs to fix. More than that, you will have to do a lot of refactoring to make the code easy to maintain. Testing and refactoring go hand in hand. On the other hand, using just testing and refactoring, I've already managed to get part of the public interface of my serializer down. Not only that, but I have an immediate idea of what my short term goals are.

The first step in assigning IDs to our types should be to enumerate the serializable types in the assembly. Writing our test first we get:
Quote:
 [Test]public void GetSerializableTypesTest() { NetworkFormatter formatter = GetFormatter(); Type[] types = formatter.GetSerializableTypes(); foreach (Type t in types) { Assert.IsTrue(t.IsSerializable, "Found non-serializable type."); }}

Then writing the associated method GetSerializableTypes()
Quote:
 internal Type[] GetSerializableTypes() { return null;}

This code also makes it compile but the test fails with a null reference exception. So lets write it to pass the test. The first step is to enumerate the types in the assembly using Assembly.GetTypes(). Then we should iterate over the array of types and figure out which ones are serializable, placing them into a List. Finally we will return an array of types, via List.ToArray(). At this point the code should look something like:
Quote:
 internal Type[] GetSerializableTypes() { Type[] types = sharedAssembly.GetTypes(); List serializableTypes = new List(); foreach (Type t in types) { if(t.IsSerializable) serializableTypes.Add(t); } return serializableTypes.ToArray();}

Now, remember in the previous entry how I mentioned that we would be sorting our entries by name? Well, this is the perfect time to do that, we can do this by adding a single line to our GetSerializableTypes() method.
Quote:
 serializableTypes.Sort(delegate(Type x, Type y) { return x.FullName.CompareTo(y.FullName); });

Quite simply, this just creates an anonymous delegate which returns the result of comparing the full type names, which are both strings. This will be called by the Sort() method, thus sorting our array by type name. Running our tests we find that we still have a red bar, investigating it we see that the TestSerialization() method isn't passing. Since we can't do anything about that test for now, we'll just have to ignore it, like so:
Quote:
 [Test, Ignore("Needs further code to fix")]public void TestSerialization() {

There are no comments to display.

## Create an account

Register a new account