logo
  • Buy
  • Download
  • Documentation
  • Blog
  • Contact

19 Dec 2013

DelphiSpec Library Announce

by Roman Yankovsky Leave a comment

Martin Fowler:

That said, I do think that the greatest potential benefit of DSLs comes when business people participate directly in the writing of the DSL code. The sweet spot, however is in making DSLs business-readable rather than business-writeable. If business people are able to look at the DSL code and understand it, then we can build a deep and rich communication channel between software development and the underlying domain.

In this post I’d like to announce DelphiSpec library. It was inspired by Cucumber. Actually, this library is the implementation of Gherkin language in Delphi. This implementation is not complete (consider it as an alpha version). Some features haven’t been implemented yet, but this project is still in development.

In a few words, the essence of the project is that DelphiSpec allows to turn the scenario into the appropriate unit test for DUnit:

Feature: Calculator

Scenario: Add two numbers
  Given I have entered 50 in calculator
    And I have entered 70 in calculator
  When I press Add
  Then the result should be 120 on the screen

Scenario: Add two numbers (fails)
  Given I have entered 50 in calculator
    And I have entered 50 in calculator
  When I press Add
  Then the result should be 120 on the screen

Scenario: Multiply three numbers
  Given I have entered 5 in calculator
    And I have entered 5 in calculator
    And I have entered 4 in calculator
  WHEN I press mul
  Then the result should be 100 on the screen

I’ll underline that these are not tests but scenarios. In other words, this is the Behavior-Driven Development (BDD) in addition to the famous Test-Driven Development (TDD). Such scenarios serve as documentation, automated tests and development-aid – all rolled into one format. You should not describe internal system logic using scenarios: they should describe how user sees an application behavior.

Use of Gherkin is a good choice for DelphiSpec because it’s now considered as de-facto standard in the particular niche and if you took interest in it you can even buy a book with the best practices, tips on writing scenarios and examples in this language. Also there is lots of information on the Internet.

Let’s return to the example (the Calculator). The scenarios execution result in DUnit will look like:

How does this work? Gherkin is very small itself. Actually its only aim is to describe Given-When-Then formula. There are several keywords, all the rest could be customized.

We test a very simple class: a calculator. I will not post its code here, you can find it on github. The interesting thing is: how will the program understand and execute commands like “Given I have entered 50 in calculator”? Here comes the helper class which describes these steps (so-called “step definitions”). This is the helper class itself:

unit SampleCalculatorStepDefs;
 
interface
 
uses
  SampleCalculator, DelphiSpec.Attributes, DelphiSpec.StepDefinitions;
 
type
  [_Feature('calculator')]
  TSampleCalculatorSteps = class(TStepDefinitions)
  private
    FCalc: TCalculator;
  public
    procedure SetUp; override;
    procedure TearDown; override;
 
    [_Given('I have entered (.*) in calculator')]
    procedure EnterInt(Value: string);
 
    [_When('I press Add')]
    procedure AddInt;
 
    [_When('I press Mul')]
    procedure MulInt;
 
    [_Then('the result should be (.*) on the screen')]
    procedure TestResult(Value: string);
  end;
 
implementation
 
uses
  System.SysUtils, TestFramework, DelphiSpec.Core;
 
{ TSampleCalculatorSteps }
 
procedure TSampleCalculatorSteps.AddInt;
begin
  FCalc.Add;
end;
 
procedure TSampleCalculatorSteps.EnterInt(Value: string);
begin
  FCalc.Push(Value.ToInteger);
end;
 
procedure TSampleCalculatorSteps.MulInt;
begin
  FCalc.Mul;
end;
 
procedure TSampleCalculatorSteps.SetUp;
begin
  FCalc := TCalculator.Create;
end;
 
procedure TSampleCalculatorSteps.TearDown;
begin
  FCalc.Free;
end;
 
procedure TSampleCalculatorSteps.TestResult(Value: string);
begin
  if FCalc.Value <> Value.ToInteger then
    raise ETestFailure.Create('Incorrect result on calculator screen');
end;
 
initialization
  RegisterStepDefinitionsClass(TSampleCalculatorSteps);
 
end.

In fact, every step has a type (Given/When/Then) and the regular expression that describes the rest part of the step. Regular expressions help to extract the parameters which will be passed to the class methods to perform the relevant part of the scenario.

The only thing we need to do so that this system works is to add into project file (.dpr) the procedure call which receives the path to a folder which contains “.feature” files to process.

begin
  PrepareDelphiSpecs(TPath.Combine(ExtractFilePath(Application.ExeName), 'features'), True, 'EN');
  DUnitTestRunner.RunRegisteredTests;
end.

The last parameter (“EN”) allows us to specify a language in which we write the script. This is another interesting Gherkin possibility. Since, generally, there is no difference in what language the text is processed using regular expressions; it would be a good idea to translate a few keywords into different languages (using this XML).

So nothing prevents us from writting scenarios in any language:

Функционал: Калькулятор

Сценарий: Сложить два числа
  Допустим я ввожу 50 на калькуляторе
         И я ввожу 70 на калькуляторе
  Когда я нажимаю СЛОЖИТЬ
  Тогда результат на экране должен быть 120

These are the very first steps, but I’m planning to implement full Gherkin functionality.

P.S. There are some comments in G+: https://plus.google.com/u/0/+RomanYankovsky/posts/2S789T49dX8

8 Dec 2013

Delphi XE5: modal forms are not modal?

by Roman Yankovsky Leave a comment

A couple of months ago Marco Cantu told us that modal forms are not directly supported on Android and we could use a new overloaded version of the same method:

    function ShowModal: TModalResult; overload;
    procedure ShowModal(const ResultProc: TProc<TModalResult>); overload;

First method does raise an exception on Android and the second can be used as follows.
This classic code:

var
  dlg: TForm1;
begin
  dlg := TForm1.Create(nil);
  try
    // select current value, if avaialble in the list
    dlg.ListBox1.ItemIndex := dlg.ListBox1.Items.IndexOf(edit1.Text);
    if dlg.ShowModal = mrOK then
      // if OK was pressed and an item is selected, pick it
      if dlg.ListBox1.ItemIndex >= 0 then
        edit1.Text := dlg.ListBox1.Items [dlg.ListBox1.ItemIndex];
  finally
    dlg.Free;
  end;
end;

should be rewritten using the new approach:

var
  dlg: TForm1;
begin
  dlg := TForm1.Create(nil);
  // select current value, if avaialble in the list
  dlg.ListBox1.ItemIndex := dlg.ListBox1.Items.IndexOf(Edit1.Text);
  dlg.ShowModal(
    procedure(ModalResult: TModalResult)
    begin
      if ModalResult = mrOK then
      // if OK was pressed and an item is selected, pick it
        if dlg.ListBox1.ItemIndex >= 0 then
          edit1.Text := dlg.ListBox1.Items [dlg.ListBox1.ItemIndex];
      dlg.DisposeOf;
    end);
end;

The idea is clear and the motivation is clear. It uses an anonymous method with the code to be executed when the “supposedly modal” form is closed. This code works on all platforms. Thanks, Marco!

But let’s take a deeper look inside this new ShowModal:

procedure TCommonCustomForm.ShowModal(const ResultProc: TProc<TModalResult>);
begin
  FResultProc := ResultProc;
  Show;
end;

The form will be not modal on any platform! Thus, if we are going to write cross-platform code, we are suggested either to use conditional compilation or to stop using modal forms at all. Seriously?

Here is the pattern that I often use in my code:

with TForm1.Create(nil) do
try
  ShowModal;
finally
  Free;
end;

Taking into account new ShowModal approach, this code can be rewritten as follows:

var
  Frm: TForm1;
begin
  Frm := TForm1.Create(nil);
  ShowModal(procedure (Res: TModalResult) begin Frm.DisposeOf; end);
end;

It works fine on all platforms, but the form is not modal anymore! So the best option for me would be:

var
  Frm: TForm1;
begin
  Frm := TForm1.Create(nil);
  {$IFDEF ANDROID}
    Frm.ShowModal(procedure (Res: TModalResult) begin end);
  {$ELSE}
    try
      Frm.ShowModal;
    finally
      Frm.Free;
    end;
  {$ENDIF}
end;

The form will be “pseudomodal” on Android and truly modal on other platforms. It’s better than nothing, but I hesitate to use IFDEFs too often – usually I have more than a couple of forms.

Finally, I have two rhetorical questions.

Why was new ShowModal approach not implemented like this:

procedure TCommonCustomForm.ShowModal(const ResultProc: TProc<TModalResult>);
begin
  {$IFDEF ANDROID}
    FResultProc := ResultProc;
    Show;
  {$ELSE}
    ResultProc(ShowModal);
  {$ENDIF}
end;

?

If this is not suitable for some reason, why did you call this method ShowModal, it has nothing to do with modal forms though?

  • Announcements
  • DelphiAST
  • DelphiSpec
  • FixInsight
  • FMX
  • Other
  • VCL

Recent Posts

  • Find leaks in Delphi with Deleaker
  • FixInsight and the inline directive
  • FixInsight 2017.04 support Delphi 10.2 Tokyo
  • FixInsight 2016.04 support Delphi 10.1 Berlin
  • FixInsight vs FMX in Delphi 10.1 Berlin

Archives

  • January 2020
  • April 2017
  • April 2016
  • March 2016
  • December 2015
  • November 2015
  • October 2015
  • September 2015
  • August 2015
  • April 2015
  • March 2015
  • February 2015
  • September 2014
  • August 2014
  • January 2014
  • December 2013
  • October 2013

Recent Comments

  • anapa-poseidon3.ru on FixInsight vs RTL
  • Heat pump on FixInsight vs RTL
  • Suing on FixInsight vs RTL
  • JorgeJag on Find leaks in Delphi with Deleaker
  • Prorabdom on FixInsight vs RTL
  • Home
  • Buy
  • Download
  • Documentation
  • Blog
  • Contact
  • © 2014-2015 SourceOddity|
  • Terms and Conditions|
  • Privacy Policy