C# + NUnit: Unit testing methods with byte array arguments
I would like to write unit tests for some classes with methods having byte array arguments. There are about 100 methods in total, and the array size ranges from 5-10 to a few 100 bytes. How should I generate and store the test arrays?
- Should I generate them manually or by some generator code (which should be unit tested, too)?
- Should I generate them in memory during the test, or should I generate them in advance and store them somewhere?
- In the latter case, should I store them in files (even if unit tests shouldn't touch the file system), or should I store them inside the test code itself (for example, in strings in hexadecimal format, like this: "47 08 00 14 etc.")?
I started to create them manually and store them in the test code in hex strings. I worked a lot with such binary strings, so I can read them relatively easily ("I don't even see the code. All I see is blonde, brunette, redhead.") The problem is, this approach is slow, and I think using an automatic generator would result in more maintainable tests. But how should I test that the output of the generator is correct? Sounds like Catch-22...
Solution 1:
I'm assuming you want the bytes to actually represent deserializable objects, not just be random. A hundred bytes or so would make a small base64 encoded string. You could save your test inputs as base 64 encoded strings and then the test could grab the correct one and turn it into bytes:
const string someScenario =
"R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==";
byte[] bytes = Convert.FromBase64String(someScenario);
You could figure out what the string would be in advance via your serializer, something like
public string SerializeAsBase64()
{
var session = new SessionCredentials { SessionKey = Guid.NewGuid() };
using (var mem = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(mem, session);
var bytes = mem.ToArray();
return Convert.ToBase64String(bytes);
}
}
To make sure it works do you have a deserializer also...? to serialize then deserialize then compare the originals are equal?
Solution 2:
I suggest using Microsofts Text Templates as generator for your unit tests. Just add a .tt file to your project and implement the generator. The generation is done at design time (when you save or change your template) and results in a .cs file in your project. Your testcode can then be handled as normal 'handwritten' code with one test per byte array and method.
The generator can load definition files as well for your byte arrays and the expected output. But since it is done at design time your unit tests are not using the file system.
The whole T4 system is really worth a look: Design-Time Code generation by using T4 Text Templates
...and it is by default part of VisualStudio - no need to install anything or change your build scripts.
Solution 3:
I would suggest a few approaches, but not sure which one is suitable or even possible for you since there are no examples of the methods you have in your case.
If you could provide an example of your method and explain its working it will be useful. What do your 100 odd methods do?
1)Try refactoring and extracting the logic which works with the byte arrays in the 100 odd methods to a few helper methods. This way the helper methods can deal with the byte arrays and then pass on the de-serialized objects to the 100 odd methods, which will now start using objects for their processing.
OR
2) Personally I prefer to have the byte arrays in the code next to the test methods, the current approach you have taken. Maybe a few combination of inputs as attributes to the functions (use NUnit RowTest extension). This makes the unit tests relatively independent of other tests. If you change parameters then only that particular unit test should be affected.
The biggest issue with a generator is that it could become very complex with logic for returning the byte arrays for each of the 100 odd methods. Changes to the generator could affect all your unit tests.
Unit tests should be easy to maintain and Independent. By introducing a generator you are adding a dependency and increasing the complexity.
Cheers!