Introduction
Organizations don't always have dedicated testers, in fact developers often have to cover their own testing. Creating test data can be a nuisance, especially when dealing with
an intricate network of a linear singular purposed large scale system. By this I mean any of the seven systems I work with on a daily basis. We all strive to have the perfect
systems created in the correct way, but unfortunately managers exist whose purpose in life is to make developers' lives difficult.
It happens, from time to time, that we need a set of data to run through the testing of a process. FakeModel, like other data creation suites available, allows us to
do so in an easy way, with a fluent api that can be likened to LINQ.
Background
There are more mature test data creation suites available, but none that I know of that will
recognize DataAnnotations and handle them appropriately, its great for creating data if using an ORM. FakeModel will pay attention to the data annotations attached to a property
and react accordingly.
But as I say, it is in it's infancy, currently at Version 0.0.5, as of last night. FakeModel was recommended to me by a University Lecturer when
I moaned that it was difficult to find a test data suite that wouldn't ignore my annotations. I don't know how he came about it.
V0.0.4 had a bug with creating properties that were a class, it would create an infinite loop so I had to keep turning this feature off,
which I suppose was easy enough;
FakeSetup.AssignClassProperty = false;
This issue has been fixed in V0.0.5, the
Release Notes say that Infinite Loops are detected and handled, by ending the creation
of new objects, which works well enough.
Using the code
FakeModel can be found on NuGet here. The HomePage can be found on Adam Riddick's website here. Whilst everything is covered, I will give a quick insight into how easy FakeModel is to use.
var myObject = Fake.Begin().Build()
This is the standard FakeModel operation, and will return an object complete with assigned properties.
We can also set specific values of certain properties, of tell FakeModel not to generate a value for them, as follows:
var myObject = Fake.Begin().Assign(x => x.Foo == "Bar").Ignore(x => x.myProp).Build();
Here we are ensuring the Foo
property of myObject
is set to the value "Bar", whilst the property
myProp
of the myObject
object will be ignored, and thus will hold
the default value for its type.
FakeModel also has many other features, such as allowing you to set Constructor Arguments, or creating them itself if you don't and they are required.
You can call methods upon creation of the object, and you can assign to Private or Readonly properties.
Unit Testing With Visual Studio Integrated Test Suite and FakeModel
I'll start of by demonstrating the creation of an object using FakeModel to be used in a Visual Studio Integrated Test Suite test scenario, this will be a simple one-model solution
at the moment, but I'll go into further detail with more in-depth scenarios after that.
One Model Scenario
In said simple situation, we will test the effectiveness of an extension method for an object, but we don't just want to test a situation that has forced
values in case using other values may throw out the code.
We start with a simple Model, named
SimpleModel
;
public class SimpleModel
{
[Range(0, 15)]
public int rangedNumber {get; set;}
public int number {get; set;}
public long result {get; set;}
}
Here we have a model that is designed (perhaps poorly) to take in a rangedNumber
, between 0 and 15, one which can then hold a result, we can assume safely that
the result will be of an unknown calculation of the two numbers.
Within the
SimpleModelExtensions
class, we have an Extension for SimpleModel
which is used to calculate
the value of result.
public static long CalculateResult(this SimpleModel model)
{
model.result = model.rangedNumber + model.numer;
return model.result;
}
We want to test a large range of numbers to ensure it isn't broken by a certain combination. Instead of generating these numbers manually,
we can use a simple loop to ensure we have an uncontrolled, random selection of data - such as the data we may well end up with over a large user base.
To do so, we use FakeModel to create a number of objects within a loop, testing that the outcome is that which is expected.
[TestMethod]
public void TestMethod1()
{
for(int i = 0; i < 1000; i++)
{
SimpleModel obj = Fake<SimpleModel>.Begin().Build();
obj.CalculateResult();
Assert.AreEqual(obj.number + obj.rangedNumber, obj.result);
Assert.IsTrue(obj.rangedNumber <= 15 && obj.rangedNumber >= 0);
}
}
Change the number from 1000 to a higher number and run the test, you will see in each case (unless
I'm yet to hit such a case) that the rangedNumber
will always
be between the ranged values. That and our extension method does its job nicely.
Albeit being a simple situation, this can show the power of using FakeModel to allow
for a number of varying data situations to be tested. In a simple model like this, testing a range could be done without too much hard work by throwing in some random numbers.
But when we need a varied number of models with larger numbers of properties, FakeModel really comes into it's own.
Multiple Model Scenario
As easy to use the simple situation was, it's a little unrealistic, you may not encounter an application using a single model in the real world. So we will build on this model
to introduce a second and third model and add some dependencies between them.
SimpleModel
will be changed so that it holds a ModelB
and a
ModelC
. ModelC
in turn will contain a ModelB
.
Let's discover if FakeModel can handle these.
Our updated SimpleModel
:
public class SimpleModel
{
[Range(0, 15)]
public int rangedNumber {get; set;}
public int number {get; set;}
public long result {get; set;}
public ModelB b {get; set;}
public ModelC c {get; set;}
}
ModelB
:
public class ModelB
{
[Range(0, 15)]
public int number {get; set;}
}
ModelC
:
public class ModelC
{
public ModelB b {get; set;}
}
We'll use another Unit Test to find out if FakeModel can handle these relationships:
[TestMethod]
public void TestMethod2()
{
for(int i = 0; i < 1000; i++)
{
SimleModel obj = Fake<SimpleModel>.Begin().Build();
ModelC objC = Fake<ModelC>.Begin().Build();
Assert.IsNotNull(obj.b);
Assert.IsNotNull(obj.c);
Assert.IsNotNull(obj.c.b);
Assert.IsTrue(obj.b.rangedNumber <= 15 && obj.b.rangedNumber >=0);
Assert.IsTrue(obj.c.b.rangedNumber <= 15 && obj.c.b.rangedNumber >=0);
}
}
We can see from this simple test that FakeModel handles POCO relationships well, and still respects properties on the next level down. But in this scenario
we are playing nice. What if we don't?
The Infinite Loop Scenario
Infinite loops happen, it's a fact. It must be up near the top of the list of the most common software development bugs (I have no facts, I just imagine that this is the case).
FakeModel claims to be able to handle Infinite Loops, as of the latest version (0.0.5) so let's see how well it does.
We will edit
ModelC
to take a SimpleModel
as well as ModelB
. SimpleModel has a
ModelC
property, which again needs a ModelC
.... and of course this goes on forever.
public class ModelC
{
public ModelB b {get; set;}
public SimpleModel simple {get; set;}
}
Using FakeModel to create either SimpleModel
or ModelC
will let us test for an infinite loop, if it doesn't end soon then it's broken.
[TestMethod]
public void TestMethod3()
{
SimpleModel obj = Fake<SimpleModel>.Begin().Build();
Assert.IsNotNull(obj);
Assert.IsNotNull(obj.b);
Assert.IsNotNull(obj.c);
Assert.IsNotNull(obj.c.Simple);
}
Running TestMethod3
will show us the results, obj.c.simple
is null, so the tests fails, but removing this line allows the test to pass.
What does that mean?
It means FakeModel has been made intuitive enough to understand that we have an infinite loop in our model. It
recognizes the loop, and decides to put an end to it at the early stage.
We have a SimpleModel
, we have a ModelC
within that
SimpleModel
, but the second layers SimpleModel
does not have it's own
ModelC
. If we change the line of the test that fails:
Assert.IsNotNull(obj.c.simple);
To check for the ModelC
's ModelB
:
Assert.IsNotNull(obj.c.b);
We see that the test passes, meaning that SimpleModel
's ModelC
's
ModelB
is still created, as it does not run a path that causes an infinite loop. It's pretty intuitive.
Points of Interest
Whilst these demonstrations show what FakeModel can do, they don't highlight it's use in real-world applications. Many such applications
become very linear very fast, especially ones specialized to a single process which itself is linear. Should you require 15 or 20 models creating in order to test the final process
in such linear systems, then FakeModel will create them for you, and what's better, it will mimic the fact that you don't ever know what your user's will enter as data, pass
these tests and you'll be ok.
What I like most about FakeModel is the author's transparency. On the FakeModel page on his personal website, he discusses where he
is planning to take FakeModel in the future, and is open to suggestions should people get in touch. I can't say how responsive he is to such suggestions, as I'm yet to try and
get in touch. That said, I am going to send in a request for Mocking of Abstract types and Interfaces.
FakeModel currently won't handle collections, but this has been promise
in a future release, and hopefully not too distant. We've also been promised a few other features, including more flexible setup options.
Should I Use It?
We have began using FakeModel in two of the applications we maintain. It's not perfect, but then again it's still pre-version 1.
FakeModel looks like it will mature like whisky and the author's plans look interesting, but who knows the rate at which these updates will come.
I would suggest using it where you need Data Annotations to be handled, but I wouldn't rely to heavily on it at this stage, most projects end in the early stages - But I really hope
this isn't one of them. I would appreciate the author getting FakeModel to a stage where he has completed what he has promised, then I would feel like it's something I could lean against.
For now, I would say take a test drive and use it where needed, and watch this space.
History
- 09/12/13 - Initial version.
- 17/12/13 - Added examples.