Original question
Let's say I have a class method GetAge(DateTime dateOfBirth)
and I want to test if the age that comes out is correct. To do this, I create a helper method called GenerateDateOfBirth(int age)
that returns a date of birth that should yield an age of age
.
However, I don't know if my GenerateDateOfBirth
method works, so I would like to bulk test a collection of ages to feed into GenerateDateOfBirth
, where each generated date of birth should yield the original age when fed back into GetAge
, something along the lines of:
[Fact]
public void CrossTestGetAgeAndGenerateDateOfBirth()
{
var ages = new List<int>(); // A reasonably large list of possible ages
var rng = new Random();
for (int i = 0; i < 1_000_000; i++)
{
ages.Add(rng.Next(0, 1000)); // Arbitrary upper bound on a person's age
}
foreach (var realAge in ages)
{
var dateOfBirth = GenerateDateOfBirth(realAge);
var calculatedAge = GetAge(dateOfBirth);
Assert.Equal(realAge, calculatedAge); // If false, at least one of the methods is bugged
}
}
I have seen that xUnit allows testing on data, by using attributes such as InlineData
and MemberData
, so I was wondering if I could generate a list of integers an then do a data-driven test on all of the "age" values like so:
public List<int> Ages { get; set; } = GetAges(0, 1000, 1_000_000); // Same arbitrary bounds as above
public List<int> GetAges(int minAge, int maxAge, int count)
{
var ages = new List<int>();
var rng = new Random();
for (int i = 0; i < count; i++)
{
ages.Add(rng.Next(minAge, maxAge));
}
return ages;
}
[Theory]
[/* something with Ages */]
public void AgeFromDateOfBirthFromAge(/* what goes here? */)
{
var randomDateOfBirth = GenerateDateOfBirth(age); // Where age comes from Ages
var calculatedAge = GetAge(randomDateOfBirth);
Assert.Equal(age, calculatedAge);
}
This seems more concise and readable and it does the same thing still. My goal (before I can write actual unit tests), is to sniff out any possible edge cases by bombarding both methods with a ton of random data. Once one of the methods seems to work well enough, ideally I can use it to fix the other and then do a final review of the (hopefully) few edge cases that might remain. Is there a way to do this?
Current implementations of both methods
Method to get age
public int GetAge(DateTime dob)
{
var now = DateTime.Now;
// Time difference up to the day
int deltaDays = now.Day - dob.Day;
int deltaMonths = now.Month - dob.Month;
int deltaYears = now.Year - dob.Year;
int age = (deltaDays + 100 * deltaMonths + 10000 * deltaYears) / 10000;
// Check if time of day has also been reached
var timeNotReached = now.TimeOfDay.Ticks < dob.TimeOfDay.Ticks;
return (deltaDays == 0 && deltaMonths == 0 && timeNotReached) ? age - 1 : age;
}
Method to generate a date of birth
public DateTime GenerateDateOfBirth(int age = 18)
{
var rng = new Random();
var now = DateTime.Now;
// Get allowed values for birth date
var maxMonth = now.Month + 1;
var maxDay = now.Day + 1;
var maxHour = now.Hour + 1;
var maxMinute = now.Minute + 1;
var maxSecond = now.Second + 1;
var maxMillisecond = now.Millisecond;
// Choose random allowed values
var birthYear = now.Year - age;
var birthMonth = rng.Next(1, maxMonth);
var birthDay = birthMonth != now.Month ? rng.Next(1, DateTime.DaysInMonth(birthYear, birthMonth) + 1) : rng.Next(1, maxDay);
var birthHour = rng.Next(1, maxHour);
var birthMinute = rng.Next(1, maxMinute);
var birthSecond = rng.Next(1, maxSecond);
var birthMillisecond = rng.Next(maxMillisecond);
var dateOfBirth = new DateTime(
birthYear,
birthMonth,
birthDay,
birthHour,
birthMinute,
birthSecond,
birthMillisecond
);
return dateOfBirth;
}