Home Unit Testing FizzBuzz in Delphi
Post
Cancel

Unit Testing FizzBuzz in Delphi

In the last article, a simple solution to the common FizzBuzz software developer interview screening test was discussed. For the majority of employers, the simple solution provided will likely be acceptable as it will be very similar to the other submissions received. However, don’t you want to be a little bit different in the interview process? Besides, there’s a healthy percentage of developer teams that insist on TDD, and if you try to join one of those teams by submitting non-testable code during the interview process, then you are going to be at a competitive disadvantage.

Introducing the first Unit Tests

I ran across a definition of Legacy Code somewhere and it has stuck with me: Legacy Code is defined simply as code without tests. (And perhaps one of the most important properties of Legacy Code is that you typically resist making any changes for fear of breaking something.) You can assume that every time that you start to write the code first instead of the test first, you are extending your legacy codebase. Perhaps future blog articles will address some of the issues of an ever-growing legacy codebase, but we’ll skip those long debates for the moment.

The solution provided in the last article was simply not easily testable. The responsibilities were intermixed as the game logic was intimately tied to the console output. The only way to test the results of our application is to parse the output displayed on the screen.

Unit Testing seems like a pretty straight-forward subject, but there are variations and nuances emphasized by different programming teams. For example, you may run into a Must-Mock team which insist on coding everything to an interface and mocking every aspect of your system during testing to enforce as much isolation as possible. You may also run into a team that uses DUnit only for building Integration Tests without a single test double in sight.

For this article, we’ll take our previous application and extend it with DUnit to provide a simple test suite to validate the FizzBuzz output. First, we’ll need to separate the console output from the value generation. (You should note that thinking about the tests first, or even better by starting to write the tests up front, this desirable separation should have been obvious.) Let’s create a new unit and move the game logic into it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
unit FizzBuzz.GameLogic;

interface

function CalcFizzBuzz(const pFizz, pBuzz, pValue:Integer):string;


implementation

uses
  System.SysUtils;


function CalcFizzBuzz(const pFizz, pBuzz, pValue:Integer):string;

  function IsMultiple(const x, y:Integer):Boolean;
  begin
    Result := (x mod y = 0);
  end;


begin
  Result := '';

  if IsMultiple(pValue, pFizz) then
  begin
    Result := 'Fizz';
  end;
  if IsMultiple(pValue, pBuzz) then
  begin
    Result := Result + 'Buzz';
  end;
  if Result = '' then
  begin
    Result := IntToStr(pValue);
  end;
end;


end.

The loop is initially moved into the DPR during this refactor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
program FizzBuzz;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  FizzBuzz.GameLogic in '..\Source\FizzBuzz.GameLogic.pas';

var
  i:Integer;
begin
  try
    for i := 1 to 100 do
    begin
      WriteLn(CalcFizzBuzz(3,5,i));
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

If you run this, you’ll get the expected outcome if you manually inspect the console window. But now let’s add some tests to automate this validation process.

Select the Project->Add New Project… menu option in the Delphi IDE (or right click the default ProjectGroup1 in the Project Explorer and select the same Add New Project… menu item.) You should be presented with a New Items dialog. Click on the Other->Unit Test category and ensure Test Project is selected and then select OK.

This should start the Test Project Wizard dialog. Let’s use the defaults for now and simply click on the Finish button. You can then add a new unit to the project which we will use for our suite of tests. In the current scenario, we have a simple procedure to test so we’ll create the test unit ourselves and not rely on the DUnit expert to generate the test suite unit.

Below is what the first test suite code might look like, and all the tests currently pass.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
unit TestFizzBuzz;

interface

uses
  TestFramework;

type

  TTestFizzBuzz = class(TTestCase)
  published
    procedure CheckFizz;
    procedure CheckBuzz;
    procedure CheckFizzBuzz;
    procedure CheckNonFizzBuzz;
  end;

const
  FizzOutput = 'Fizz';
  BuzzOutput = 'Buzz';
  FizzVal = 3;
  BuzzVal = 5;


implementation
uses
 FizzBuzz.GameLogic;

procedure TTestFizzBuzz.CheckFizz;
begin
  CheckEqualsString(FizzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal));
  CheckEqualsString(FizzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal*2));
  CheckEqualsString(FizzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal*3));
  CheckEqualsString(FizzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal*101));
end;

procedure TTestFizzBuzz.CheckBuzz;
begin
  CheckEqualsString(BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, BuzzVal));
  CheckEqualsString(BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, BuzzVal*2));
  CheckEqualsString(BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, BuzzVal*4));
  CheckEqualsString(BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, BuzzVal*101));
end;

procedure TTestFizzBuzz.CheckFizzBuzz;
begin
  CheckEqualsString(FizzOutput+BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal*BuzzVal));
  CheckEqualsString(FizzOutput+BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal*BuzzVal*2));
  CheckEqualsString(FizzOutput+BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal*BuzzVal*3));
  CheckEqualsString(FizzOutput+BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal*BuzzVal*4));
  CheckEqualsString(FizzOutput+BuzzOutput, CalcFizzBuzz(FizzVal, BuzzVal, FizzVal*BuzzVal*5));
end;

procedure TTestFizzBuzz.CheckNonFizzBuzz;
begin
  CheckEqualsString('1', CalcFizzBuzz(FizzVal, BuzzVal, 1));
  CheckEqualsString('2', CalcFizzBuzz(FizzVal, BuzzVal, 2));
  CheckEqualsString('4', CalcFizzBuzz(FizzVal, BuzzVal, 4));
  CheckEqualsString('7', CalcFizzBuzz(FizzVal, BuzzVal, 7));
end;

initialization
  RegisterTest(TTestFizzBuzz.Suite);

end.

For a quick code screening test, adding some unit tests like this to the FizzBuzz project could help distinguish you from the other applicants. (But if you started with the tests first you would not have to refactor later.) Let’s not affix our ‘Must-Mock’ badges on our shirts just yet and over-engineer the solution, but let us see if we can quickly improve on the code above.

Rethinking FizzBuzz

Creating a simple procedure is a common solution to many Delphi tasks. As we shown above, these simple procedures can certainly be testable - and if you are just going to get started with Unit Testing, this may be a valid first step. However, the DUnit tooling is built around testing classes, so you will inherit some minor benefits by simply switching to class based development.

Let’s refactor the CalcFizzBuzz method into a class. This takes your code embedded into a single procedure and allows it to be more extensible in the future.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
unit FizzBuzz.GameLogic;

interface

type

  TFizzBuzzGame = class
  public const
    FizzText = 'Fizz';
    BuzzText = 'Buzz';
  private const
    defFizzValue = 3;
    defBuzzValue = 5;
  private
    fFizz:Integer;
    fBuzz:Integer;
  protected
    function IsMultiple(const x, y:Integer):Boolean;
  public
    constructor Create();

    function CalcFizzBuzz(const pValue:Integer):string;

    property Fizz:Integer read fFizz write fFizz;
    property Buzz:Integer read fBuzz write fBuzz;
  end;


implementation

uses
  System.SysUtils;


constructor TFizzBuzzGame.Create();
begin
  inherited;
  fFizz := defFizzValue;
  fBuzz := defBuzzValue;
end;


function TFizzBuzzGame.IsMultiple(const x, y:Integer):Boolean;
begin
  Result := (x mod y = 0);
end;


function TFizzBuzzGame.CalcFizzBuzz(const pValue:Integer):string;
begin
  Result := '';

  if IsMultiple(pValue, Fizz) then
  begin
    Result := FizzText;
  end;
  if IsMultiple(pValue, Buzz) then
  begin
    Result := Result + BuzzText;
  end;
  if Result = '' then
  begin
    Result := IntToStr(pValue);
  end;
end;


end.

As you can see, it’s nearly identical code to the original simple procedure but it’s laid out in a way that makes it easier to work with. It seems pretty minor in this small bit of code, but sometimes small improvements go a long way.

This new class forces some minor changes to the DPR file to utilize this new class. Perhaps we could extract the loop into a GameSession in a future modification. And perhaps we could extract most of this code out of the DPR… it all depends on how much effort and time you have to apply to this task.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
program FizzBuzz;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  FizzBuzz.GameLogic in '..\Source\FizzBuzz.GameLogic.pas';

var
  i:Integer;
  vGame:TFizzBuzzGame;
begin
  try
    vGame := TFizzBuzzGame.Create();
    try
      for i := 1 to 100 do
      begin
        WriteLn(vGame.CalcFizzBuzz(i));
      end;
    finally
      vGame.Free();
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

The test unit is updated as well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
unit TestFizzBuzz;

interface

uses
  TestFramework,
  FizzBuzz.GameLogic;

type

  TestTFizzBuzzGame = class(TTestCase)
  private const
    TestFizzVal = 3;
    TestBuzzVal = 5;
  strict private
    fGame:TFizzBuzzGame;
  public
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure CheckFizz;
    procedure CheckBuzz;
    procedure CheckFizzBuzz;
    procedure CheckNonFizzBuzz;
    procedure CheckFizzText;
    procedure CheckBuzzText;
  end;


implementation


procedure TestTFizzBuzzGame.SetUp;
begin
  fGame := TFizzBuzzGame.Create;
  fGame.Fizz := TestFizzVal;
  fGame.Buzz := TestBuzzVal;
end;


procedure TestTFizzBuzzGame.TearDown;
begin
  fGame.Free;
  fGame := nil;
end;


procedure TestTFizzBuzzGame.CheckFizz;
begin
  CheckEqualsString(fGame.FizzText, fGame.CalcFizzBuzz(TestFizzVal));
  CheckEqualsString(fGame.FizzText, fGame.CalcFizzBuzz(TestFizzVal * 2));
  CheckEqualsString(fGame.FizzText, fGame.CalcFizzBuzz(TestFizzVal * 3));
  CheckEqualsString(fGame.FizzText, fGame.CalcFizzBuzz(TestFizzVal * 101));
end;


procedure TestTFizzBuzzGame.CheckBuzz;
begin
  CheckEqualsString(fGame.BuzzText, fGame.CalcFizzBuzz(TestBuzzVal));
  CheckEqualsString(fGame.BuzzText, fGame.CalcFizzBuzz(TestBuzzVal * 2));
  CheckEqualsString(fGame.BuzzText, fGame.CalcFizzBuzz(TestBuzzVal * 4));
  CheckEqualsString(fGame.BuzzText, fGame.CalcFizzBuzz(TestBuzzVal * 101));
end;


procedure TestTFizzBuzzGame.CheckFizzBuzz;
begin
  CheckEqualsString(fGame.FizzText + fGame.BuzzText, fGame.CalcFizzBuzz(TestFizzVal * TestBuzzVal));
  CheckEqualsString(fGame.FizzText + fGame.BuzzText, fGame.CalcFizzBuzz(TestFizzVal * TestBuzzVal * 2));
  CheckEqualsString(fGame.FizzText + fGame.BuzzText, fGame.CalcFizzBuzz(TestFizzVal * TestBuzzVal * 3));
  CheckEqualsString(fGame.FizzText + fGame.BuzzText, fGame.CalcFizzBuzz(TestFizzVal * TestBuzzVal * 4));
  CheckEqualsString(fGame.FizzText + fGame.BuzzText, fGame.CalcFizzBuzz(TestFizzVal * TestBuzzVal * 5));
end;


procedure TestTFizzBuzzGame.CheckNonFizzBuzz;
begin
  CheckEqualsString('1', fGame.CalcFizzBuzz(1));
  CheckEqualsString('2', fGame.CalcFizzBuzz(2));
  CheckEqualsString('4', fGame.CalcFizzBuzz(4));
  CheckEqualsString('7', fGame.CalcFizzBuzz(7));
end;


procedure TestTFizzBuzzGame.CheckFizzText;
begin
  CheckEqualsString('Fizz', fGame.FizzText);
end;


procedure TestTFizzBuzzGame.CheckBuzzText;
begin
  CheckEqualsString('Buzz', fGame.BuzzText);
end;


initialization

RegisterTest(TestTFizzBuzzGame.Suite);

end.

Running the tests and seeing all green is good! We know we haven’t screwed up the basic operation of our FizzBuzz solution.

FizzBuzz Unit Tests - All Tests Pass

Summary

If you were only given a short amount of time to whip out a FizzBuzz solution in a job interview, you will probably end up with a little time to refactor. As you can see above, it doesn’t take much effort to change from a simple procedure to a class and this process helps to clean up some of the code and allow for easier modifications. And if you provide testable code up front, you are nearly guaranteed to be ahead of other applicants (besides helping to ensure that your little code test actually works as intended!) In addition, some employers may ask for small modifications to your FizzBuzz solution in order to see how quickly and easily you can handle change requests. For example, they may ask for all values divisble by 2 to return “Flop”. If you have the test infrastructure already in place, it helps to avoid some silly mistakes when you are making quick changes in a potentially stressful situation like a job interview.

There’s plenty more we can do with this silly FizzBuzz code. Perhaps more articles will be posted in the near future based on feedback.

DUnitX

You shouldn’t mention unit testing in Delphi without a reference to DUnitX. This is currently the gold standard in unit testing within the Delphi community. It’s available in the later versions of Delphi, or simply grab it from GitHub. It’s simply much more powerful than the built in DUnit framework as it’s built for Generics, Extended RTTI, attribute based testing and more.