At the moment, when I create a public class called Foo, I also create a static test class called FooTester which exercises the public members of the class as best I can.
For example, my non-contiguous range class Range has a RangeTest class that looks like this:
public static string Run()
{
try
{
testsRun = 0;
testsPassed = 0;
testsFailed = 0;
outText.Clear();
Range ra = new Range(0, 4);
Range rb = new Range(5, 9);
Range rc = new Range(10, 14);
Range rd = new Range(1, 3);
Range re = new Range(6, 8);
Range rf = new Range(16, 19);
Range rg = new Range(0, 0);
Range rh = new Range(1, 1);
Range ri = new Range(2, 2);
Range rj = new Range(0, 19);
Range rk = new Range(0, 4);
CheckInt(ra.Count, 5);
CheckInt(ra.Min, 0);
CheckInt(ra.Max, 4);
CheckString(ra, "(0:4)");
CheckString(ra.UnionWith(re), "(0:4) | (6:8)");
Check(Range.GetEmpty(), new int[0]);
Check(ra, new int[] { 0, 1, 2, 3, 4, });
Check(new Range(new int[] { 0, 1, 2, 5, 6, 7 }.AsEnumerable()), new int[] { 0, 1, 2, 5, 6, 7 });
Check(ra.UnionWith(ra), new int[] { 0, 1, 2, 3, 4 });
Check(ra.UnionWith(rb), new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
Check(rb.UnionWith(rc), new int[] { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 });
Check(ra.UnionWith(rc), new int[] { 0, 1, 2, 3, 4, 10, 11, 12, 13, 14 });
Check(ra.UnionWith(re), new int[] { 0, 1, 2, 3, 4, 6, 7, 8 });
Check(ra.UnionWith(rg), new int[] { 0, 1, 2, 3, 4 });
Check(rb.UnionWith(rg), new int[] { 0, 5, 6, 7, 8, 9 });
Check(rg.UnionWith(rh).UnionWith(ri), new int[] { 0, 1, 2 });
CheckInt(ra.UnionWith(rc).Count, 10);
CheckInt(ra.UnionWith(rc).Min, 0);
CheckInt(ra.UnionWith(rc).Max, 14);
Check(ra.Intersecting(ra), new int[] { 0, 1, 2, 3, 4 });
Check(ra.Intersecting(rb), new int[0]);
Check(rb.Intersecting(rc), new int[0]);
Check(ra.Intersecting(rc), new int[0]);
Check(ra.Intersecting(rg), new int[] { 0 });
Check(rb.Intersecting(rg), new int[0]);
Check(rg.Intersecting(rh).Intersecting(ri), new int[0]);
Check(ra.Excepting(ra), new int[0]);
Check(ra.Excepting(rb), new int[] { 0, 1, 2, 3, 4 });
Check(rb.Excepting(rc), new int[] { 5, 6, 7, 8, 9 });
Check(ra.Excepting(rc), new int[] { 0, 1, 2, 3, 4 });
Check(ra.Excepting(rg), new int[] { 1, 2, 3, 4 });
Check(rj.Excepting(rh).Excepting(rb).Excepting(rf), new int[] { 0, 2, 3, 4, 10, 11, 12, 13, 14, 15 });
Check(rg.Excepting(rh).Excepting(ri), new int[] { 0 });
Check(ra.EndAt(rj.Max), new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 });
Check(rf.StartAt(ra.Min), new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 });
Check(ra.UnionWith(re).MakeContiguous(), new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 });
Check(ra.Add(5), new int[] { 0, 1, 2, 3, 4, 5 });
Check(ra.Add(-1), new int[] { -1, 0, 1, 2, 3, 4 });
Check(ra.AddRange(new int[] { 5, -1 }), new int[] { -1, 0, 1, 2, 3, 4, 5 });
Check(ra + 5, new int[] { 0, 1, 2, 3, 4, 5 });
Check(ra + -1, new int[] { -1, 0, 1, 2, 3, 4 });
Check(ra + new int[] { 5, -1 }, new int[] { -1, 0, 1, 2, 3, 4, 5 });
Check(ra + rb, new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
Check(ra.Remove(1), new int[] { 0, 2, 3, 4 });
Check(ra.RemoveRange(new int[] { 1, 2 }), new int[] { 0, 3, 4 });
Check(ra - 1, new int[] { 0, 2, 3, 4 });
Check(ra - -1, new int[] { 0, 1, 2, 3, 4 });
Check(ra - new int[] { 1, 2 }, new int[] { 0, 3, 4 });
Check(ra - rd, new int[] { 0, 4 });
CheckBool(ra.Equals(rk), true);
CheckBool(ra.Equals((object)rk), true);
CheckBool(ra == rk, true);
CheckBool(ra != rk, false);
CheckBool(ra.IsContiguousWith(rb), true);
CheckBool(ra.IsContiguousWith(rc), false);
CheckBool(ra.IsContiguousWith(rd), false);
CheckBool(ra.IsIntersecting(rb), false);
CheckBool(ra.IsIntersecting(rc), false);
CheckBool(ra.IsIntersecting(rd), true);
}
catch (Exception ex)
{
outText.AppendLine($"Exception in testing: \"{ex.Message}\"");
}
outText.AppendLine($"Tests: {testsRun}: Passed: {testsPassed} Failed: {testsFailed}");
return outText.ToString();
}
along with the service methods it needs to check and report:
private static void CheckBool(bool result, bool expected)
{
testsRun++;
if (result == expected)
{
testsPassed++;
return;
}
ReportFailure("Logical test failed", result.ToString(), expected.ToString());
}
private static void CheckInt(int result, int expected)
{
testsRun++;
if (result == expected)
{
testsPassed++;
return;
}
ReportFailure("Integer comparison failed", result.ToString(), expected.ToString());
}
private static void CheckString(Range range, string expected)
{
testsRun++;
string strRange = range.ToString();
if (strRange == expected)
{
testsPassed++;
return;
}
ReportFailure("Human readable data did not match", strRange, expected);
}
private static void Check(Range range, int[] expected)
{
testsRun++;
string result = "Range length incorrect";
if (range.Count == expected.Length)
{
result = "Range values did not match";
if (Enumerable.SequenceEqual(range, expected))
{
testsPassed++;
return;
}
}
ReportFailure(result, range, expected);
}
private static void ReportFailure(string result, string range, string expected)
{
testsFailed++;
outText.AppendLine($"Test #{testsRun}: {result} ");
outText.AppendLine($" Got : {range}");
outText.AppendLine($" Expected: {expected}");
}
private static void ReportFailure(string result, Range range, int[] expected)
{
testsFailed++;
outText.AppendLine($"Test #{testsRun}: {result} ");
outText.AppendLine($" Got : {string.Join(", ", range.Select(r => r.ToString()))}");
outText.AppendLine($" Expected: {string.Join(", ", expected.Select(r => r.ToString()))}");
}
}
And it works: I throw every possibility I can think of in there together with the expected result, and I get a message saying it passed or where it failed.
I've been doing this for decades - it allows me to run tests to show a bug exists, and that it's fixed without breaking any other tests.
But as I said, I'm old fashioned sometimes - and along came Unit Testing and I ignored it completely. But should I have? Or should I think of biting bullets and embracing the change?
Anyone using it? Using something like what I do? Or are you all philistines who don't automate tests at all?
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony
"Common sense is so rare these days, it should be classified as a super power" - Random T-shirt
AntiTwitter: @DalekDave is now a follower!
|