2020-07-29
  •  
  •  

How To Prepare Test Data For Unit Test

There are many articles describing how to write a good test. But the question is how to create a good data test? I will show an example, different from what you may have ever read. A few months ago, I developed a module for checking if the weight of a set of vehicles is too high or not. The module has been prepared according to the Polish law. Subsequent paragraphs define several dozen cases. The set of vehicles can be treated as one entity or each of them separately.

The very first decision was to assume whether each paragraph act should be a separate rule (block) in a code. Because there are plenty of rules, I decided to create a specific response class. This could tell if a paragraph is fulfilled or not:

public class Info
{
        public string Paragraph { get; set; }
        public decimal CurrentValue { get; set; }
        public decimal Limit { get; set; }
        public int? AxisNumber { get; set; }
        public bool IsValid => CurrentValue <= Limit;
private Info(string paragraph)
        {
            Paragraph = paragraph;
        }
public static Info Paragraph(string paragraph)
        {
            return new Info (paragraph);
        }
}

Algorithm preparation

Now I was able to return with information about which rule is exceeded or not. What is the limit for the current rule? What is the current weight of a set of vehicles or axis pressure? If it is possible which axis is exceeded? Now I was ready to collect all the rules and relevant factors. It could be described with the following questions:

  • Is it a car and a trailer or a tractor and a semi-trailer?
  • How many axes does a car or a tractor have?
  • How many axes does a trailer or semi-trailer have?
  • Is there a pneumatic suspension?
  • Which axis is a drive axis?
  • Are there any twin tires?
  • What are axis pressures?
  • What is a total weight?
  • What is the distance between axes?
  • How many axes are there in the group of axes?
  • What is the pressure of each axis in the group?

As you can see there are many parameters and combinations. In this situation, a unit test is a must. Let’s start coding with 100% test code coverage.

Objects creation

First of all, I wanted to create a code concept that would allow an easy creation of subsequent test cases, as well as real objects. I decided that it would be best to use the Builder Design Pattern for this purpose. In our case, it is important that the set of vehicles consists of a car or a tractor and a trailer or a semi-trailer, respectively. Each axis has its own pressure. The sum of these pressures is the weight of individual vehicles.

This is already enough to create the builder class that will help us create a set of vehicles:

internal class VehicleSetBuilder
{
 public static VehicleSetBuilder Create()
 {
  return new VehicleSetBuilder();
 }
public VehicleSet Build() => vehicleSet;
public VehicleSetBuilder AddCarAxis(Axis axis)
 {
  vehicleSet.Axes.Insert(vehicleSet.Car.AxisCount, axis);
  vehicleSet.Car.AxisCount++;
  return this;
 }
public VehicleSetBuilder AddTrailerAxis(Axis axis)
 {
  vehicleSet.Axes.Add(axis);
  vehicleSet.Trailer.AxisCount++;
  return this;
 }
public VehicleSetBuilder IsSemiTrailer()
 {
  vehicleSet.Trailer.IsSemiTrailer = true;
  return this;
}
internal VehicleSetBuilder IsTractor()
 {
  vehicleSet.Car.IsTractor = true;
  return this;
 }
internal VehicleSetBuilder HasPneumaticSuspensjon()
 {
  vehicleSet.IsPneumaticSuspensjon = true;
  return this;
 }
}

And a single car axle:

internal class AxisBuilder
{
 public static AxisBuilder Create()
 {
  return new AxisBuilder();
 }
 public Axis Build() => axis;
public AxisBuilder SetPresure(decimal presure)
 {
  axis.PressureWithLoad = presure;
  return this;
 }
public AxisBuilder IsDriveAxis()
 {
  axis.IsDrive = true;
  return this;
 }
public AxisBuilder HasDoubleTires()
 {
  axis.Wheels = 4;
  return this;
 }
}

Now if we want to create a set of vehicles with specific parameters, consisting of, for example:

Tractor with two axes, where axis loads are 8 and 9 tonnes respectively and the first of the axes is driving, and a semi-trailer with three axes, each of which has 8.5 tonnes pressure equipped with a pneumatic suspension, just write the code:

var vehicleSet = VehicleSetBuilder
 .Create() .AddCarAxis(AxisBuilder.Create().SetPresure(8M).IsDriveAxis().Build())
 .AddCarAxis(AxisBuilder.Create().SetPresure(9M).Build())
 .AddTrailerAxis(AxisBuilder.Create().SetPresure(8.5M).Build())
 .AddTrailerAxis(AxisBuilder.Create().SetPresure(8.5M).Build())
 .AddTrailerAxis(AxisBuilder.Create().SetPresure(8.5M).Build())
 .HasPneumaticSuspensjon()
 .IsTractor()
 .IsSemiTrailer()
 .Build();

This way of creating a set of vehicles allows you to create transparent test cases and, of course, real objects in the application.

The main class of the algorithm

Now let’s add a class, which task will be to check whether a vehicle set exceeds the permissible standards separately for each of the cases described in the regulations. The most complex rules, which define groups of axes, based on different distances between individual axes and define limits depending on the number of axes in the group and the distance between them. For the sake of simplicity, they will be omitted.

public class VehicleSetChecker
{
 public VehicleSetChecker(VehicleSet vehicleSet)
 {
  resultChecker = new List<Info>();
  this.vehicleSet = vehicleSet;
}
 
 public List<Info> IsOverWeight()
 {
  resultChecker.Append(VehicleSet3Axes ());
  resultChecker.Append(VehicleSet4AxesCar2Axes ());
  return resultChecker;
 }
private Info VehicleSet3Axes()
 {
  const string PARAGRAPH = "§1. 1.";
  if (vehicleSet.TotalAxes == 3)
  {
   return Info.Paragraph(PARAGRAPH).Result(vehicleSet.TotalWeight, 28);
  }
  return null;
 }
private Info VehicleSet4AxesCar2Axes()
 {
  const string PARAGRAPH = "§1. 2.";
  if (IsCarWithAxesAndTrailerAxes(2, 2))
  {
   return Info.Paragraph(PARAGRAPH).Result(vehicleSet.TotalWeight, 36);
  }
  return null;
 }
}

(The rest of rules were omitted)

First Test

Now we can create our first test:

const string PARAGRAPH = "§1. 1.";
[TestMethod]
public void WehicleSetWithThreeAxisTest()
{
 var vehicleSet = VehicleSetBuilder
  .Create()
  .AddCarAxis(AxisBuilder.Create().SetPresure(9.5M).IsDriveAxis().HasDoubleTires().Build())
  .AddCarAxis(AxisBuilder.Create().SetPresure(9.5M).IsDriveAxis().HasDoubleTires().Build())
  .AddTrailerAxis(AxisBuilder.Create().SetPresure(9.5M).Build()
  .HasPneumaticSuspensjon()
  .Build();
VehicleSetChecker overWeightChecker = new VehicleSetChecker(vehicleSet);
 CheckerResultList result = overWeightChecker.IsOverWeight();
 Assert.IsTrue(result.HasParagraphReason(PARAGRAPH));
 Assert.IsFalse (result.IsValid);
}

First we create a simple vehicle set for checking separate rules. All the tests look very similar.

Second, we create a test object. In the above case, it will be a combination of vehicles with a total weight of 28.5 tones. Then we initialize the class, which is responsible for all checks, we perform the check as a result of which we get a list of Info objects. Finally, we check whether the vehicle has been correctly marked as exceeding the weight and whether the collection of Info objects contains the error information resulting from rule 1.1.

Summary

Such a test development path and the entire verification algorithm allows to create both tests to validate for:

  • a particular regulation – by verifying a vehicle set slightly exceeding the permissible standards and the vehicle set that is in the upper limit of these standards,
  • all regulations at once – by verifying whether the entire vehicle does not exceed any standards,
  • each case asked to be verified by the client. Creation of a very simple test confirmed the correctness of the implementation – the algorithm returns a precise information about the reason for exceeding the standards.

Designing applications and tests at the same time made it possible to prepare such a set of classes that facilitated the creation of further test scenarios and their verification through unit tests.

Despite the high complexity of the algorithm, only a few comments returned from the client about the created module:

  • a few ended up indicating the paragraph, which is the reason to mark the set of vehicles as exceeding the standards,
  • one concerned the omission of a specific rule contained in the transitional provisions,
  • one specific case, which required a professional opinion of a legal expert regarding the interpretation of a law – unfortunately I had to agree with the lawyers. Maybe the act is written in an ambiguous way, and only with knowledge and experience, it can be interpreted correctly.

When creating modules, that we intend to test with unit tests, it is also a good idea to plan it for providing data tests. This will allow you to create realistic tests. It will facilitate not only the creation of both tests but also the implementation of algorithms.

What is the pressure of each axis in the group?

 

Written by Paweł Szymura
Senior developer and technical leader at Evertop. Coding since 10 years old.
Personally, photographer and squash player.