Testing

This section will detail how to implement a testing strategy with regard to eventual texts shown by the commands implemented by the developer, as long as the developer uses the TCommandBuilder callbacks. Another possibility is to configure callbacks for user input through the callback property.

It is not the aim of this section to cover the entire testing strategy of the application being developed, but only what is connected with the use of the library.

You can consult the tests implemented for the library itself and see how the output and input routines have been replaced by callbacks that capture the content and divert the console output to variables and simulate a user's input preventing the test from simply getting stuck.

It will be considered that the developer has experience using the fpcunit testing framework.

Unit tests using Output

Considering that a test project already exists and that a unit implementing a TTestCase class is also available, we can proceed with the instructions below.

To test the Output of the commands executed by the application, we can follow the steps:

Preparing for setup

Add the units Command.Interfaces, Command.Builder and StrUtils to your unit that implements the TTestCase class:

uses
  Command.Interfaces,
  Command.App,
  Command.Builder,
  StrUtils;

Don't forget to add your unit that implements the command to be tested to uses clause too.

Declare a field on TTestCase private section to hold an TCommandApp instance:

private
  FApplication: TCommandApp;

In the implementation section, you need to declare a variable to capture the output content and also a funcion callback to assign the text output to this variable:

var
  CapturedOutput: string;

procedure MockOutput(const AMessage: string);
begin
  CapturedOutput := CapturedOutput + AMessage + #13#10;
end;

Adjust the setup

The setup procedure of unit test class will be called for each unit test, so we need to intiliaze it properly.

procedure TTestMyCommand.SetUp;
begin
  FApplication := TCommandApp.Create(nil);
  FApplication.Title := 'testcmdapp';
  // change the Output callback with our Mock, so we can capture the command outputs
  FApplication.CommandBuilder.Output := @MockOutput; 
  // clear possible other tests output
  CapturedOutput := ''; 
end;

Adjust the TearDown

We need to free the FApplication from memory to avoid leaks and have tests run independently of the other tests.

procedure TTestMyCommand.TearDown;
begin
  FreeAndNil(FApplication);
end;

Implementing a test

We can implement a simple test using what has been prepared. Create a new test and use the following code:

procedure TTestMyCommand.TestMyCommandBasic;
var
  LExpected: string;
begin
  // arrange
  FApplication
    .CommandBuilder
      // configure your command callback
      .AddCommand('mycommand', 'mycommand description', @MyCommand, []) 
        .AddOption('m', 'my-option', 'my option description', []);

  // act
  FApplication
    .CommandBuilder
      // Inject arguments to simulate an application call made by the user
      .UseArguments(['mycommand', 'm']) 
      .Parse;

  // Here the CommandBuilder effectively calls the callback
  FApplication.CommandBuilder.Execute; 

  // asserts
  LExpected := 'my expected output''my expected output'#13#10;
  AssertEquals(LExpected, CapturedOutput);
end;

This test, in addition to testing the execution of the command, is also testing if the command is called by the CommandBuilder when a certain set of parameters are passed by the user. It is worth noting the use of the UseArguments method, its purpose is to inject arguments into the CommandBuilder, without the user having to provide them, which helps a lot in tests.

Unit tests using Input

To implement a test case to test the user input of a given command we will use the work already done in the previous test in terms of preparation and setup.

Intial changes

We need to declare a variable to simulate a specific input from the user, and also provide a procedure simulate the user input. To do that add the following code to the unit implementation section:

var
  MockInputLnResult: string;

function MockInputLn: string;
begin
  Result := MockInputLnResult;
end;

Adjust the setup

We need to change the callback to our MockInputLn, and also initialize the variable MockInputLnResult, add these lines to TTestMyCommand.Setup method:

FApplication.CommandBuilder.InputLn := @MockInputLn;
MockInputLnResult := '';

Implementing a test

Your command should call CommandBuilder.InputLn as we see below:

procedure MyCommandWithInput(ABuilder: ICommandBuilder);
var
  LAnswer: string;
begin
  ABuilder.Output('Are you sure? [y], [n]: ');
  LAnswer := ABuilder.InputLn();
  if LAnswer = 'y' then 
    ABuilder.Output('Your answer was: ' + LAnswer)
    // do MyCommand stuff
  else
    ABuilder.Output('Command aborted');
end;

Now we can implement our test and simulate user input, so that the command can be run until the end:

procedure TTestMyCommand.TestMyCommandBasicWithInput;
var
  LExpected: string;
begin
  // arrange
  FApplication
    .CommandBuilder
      // configure your command callback
      .AddCommand('mycommand', 'mycommand description', @MyCommandWithInput, []) 
        .AddOption('m', 'my-option', 'my option description', []);

  // Simulates that the user entered the answer "y"
  MockInputLnResult := 'y';

  // act
  FApplication
    .CommandBuilder
      // Inject arguments to simulate an application call made by the user
      .UseArguments(['mycommand', 'm']) 
      .Parse;

  FApplication.CommandBuilder.Execute; // Here the CommandBuilder effectively calls the callback

  // asserts
  AssertTrue(MockInputLnResult, ContainsText(CapturedOutput, 'Your answer was: ' + MockInputLnResult));
end;

Summary

We saw how to implement tests using library resources, but we do not deal here with tests considering the application execution via the command line. This could be done as integration tests.

In the next topic we can explore the organization of the pascli project in terms of source code, IDEs used, prerequisites, among others.

Next About the project


Generated by PasDoc 0.16.0.