Original in Russian: http://18delphi.blogspot.ru/2013/11/5.html
GUI-testing. Table of contents
Now I want to explain “what is written there” and “why we need it”.
Actually, I wanted to give an example of how to test designed classes on our own, without DUnit (http://18delphi.blogspot.ru/2013/03/dunit_29.html), mock's (http://18delphi.blogspot.ru/2013/10/gui.html) and other (http://18delphi.blogspot.ru/2013/04/blog-post_8791.html) “odd staff”. .
I have been doing it for LONG TIME till I have mastered DUnit (http://18delphi.blogspot.ru/2013/03/blog-post.html).
How can we “simply take our designed class and test it” (http://18delphi.blogspot.ru/2013/04/tdd-delphi.html).
It should be noted – I am not looking for a “silver bullet”. As well as “spherical cows in vacuum”.
It should be clear that what I speak about is UNIVERSAL SOLUTIONS. They do NOT exist.
Once again – do NOT exist. UNIVERSAL SOLUTIONS.
If you start to look for “the disadvantages of the approach” and say “these were all particular cases”, I will note – YES, THESE ARE “particular cases”.
The WHOLE TESTING is made up of PARTICULAR CASES.
Some partial cases are derived from system requirements, some – from library specifications, some – from the already found errors.
These partial cases – EITHER WAY cover the tested system.
Absolute is a MYTH!
As well as a “silver bullet”.
But. “ANY boss-eyed and half-working, but WRITTEN test is BETTER than a million of PERFECT but UNWRITTEN ones”.
I want you to understand it.
If you still do not understand, it means the time FOR YOU to test has not yet arrived.
Probably, you “sleep in peace” as it is. The errors in the code do not bother you.
I’ll continue.
ANY test (I speak of) – AT LEAST allows you to determine the “regression” (https://en.wikipedia.org/wiki/Regression_testing), i.e. – to control the fact that in the system you develop when you change the code – NOTHING (tested already) gets out of order.
So.
I’ll try to analyze it all using the example.
First, I will ASSUME ONE THING.
We’ll analyze the testing of the DETERMINED code (https://en.wikipedia.org/wiki/Deterministic_algorithm) . on its “generalized parameters” (including, say, Self).
Let’s define the methods we’ll work with:
The particular object to be tested is TscriptParser.
Let its specification be as follows:
TscriptParser is a tool to parse the input symbol stream, which has to possess the following properties:
1. It parses the input stream for “tokens” (a sequence of “not empty characters”).
2. A “token” cannot move “between lines”. As “end-of-line” indicator the characters with codes 13 and 10 or their combination serve.
3. The empty characters are “space”, “tabbing” and (basing on the previous example) – characters with codes 13 and 10.
4. The “class” of tokens – “strings enclosed in single quotes” - is singled out specially. These tokens are featured by the “single quotes at the beginning” and the “single quotes at the end”.
5. Let our requirements specification (RS) keep secret of “double quotes” and “doubled single quotes”.
6. There are “commentaries” – ANY text in the file line beginning from the substring “//” and up to the end of the current line has to be IGNORED. It does not have to appear in the list of “tokens”.
7. Token does not have to be empty. It means token has to include at least ONE “not empty character”.
Actually, the given RS determines the code of TscriptParser, which is given in the previous series. (Within the quotes.)
And now I’ll tell how TscriptParser is organised:
I’ll note straight – this is NOT an EXAMPLE of how Parsers SHOULD BE written.
Once again – NOT an EXAMPLE of how you SHOULD write Parsers.
Were I not bound to the example, I would have written the code in absolutely different way. Using libraries, with internal buffer, address arithmetic and lambdas during parsing for “tokens”.
This refers to the fact that you don’t have to be “too captious” to the parser code.
Now, let’s see how it is organized.
It is made of “a number of layers”:
- GetChar – reads the character appearing in the input stream and gives message about file end if we reach it.
- ReadLn – is based on GetChar; reads the current line of the input stream from the current position to the file end or the “end-of-line” indicator. It also “preprocesses” the read line for “line comments” and deleting them.
- NextToken – is based on ReadLn and parsers the current line for tokens in accordance with the rules described above.
Now, let’s get to testing.
Previously I have written about the concept of “checkpoints” - in first chapter .
Later we’ll consider it.
Now, we’ll define the concepts we need for testing:
1. Test engine (TtestEngine) – an “engine” to determine the state of the tested system.
2. Tested system – the code that we test using test engine (unfortunately, the tautology is inevitable). In our case the tested system is our class TscriptParser.
3. Experiment or test (TtestInstance) - a particular experiment of the tested system for FIXED input parameters. Let’s assume that experiments can’t be nested. It means, all code of tests we describe can carry out only ONE experiment at the interval of [t1..t2].
4. “Checkpoint” or “connection socket” (TtestSocket) – NAMED (identifiable, differentiated from the similar ones) object describing the special code fragment of the tested system.
5. “Measured value” (TtestValue) – a particular value taken from the “connection socket” at the moment.
6. Socket metric (TtestSocketMetric) – an array of “measured values” (TtestValue and TtestMetricValues) taken from the SPECIFIC socket during the WHOLE experiment.
Let us think that the methods of classes TscriptParser - GetChar, ReadLn and NextToken described above are the IMPORTANT spots of the tested system for taking readings using test engine.
I.e. these spots are the “checkpoints”.
How it looks in code:
The test engine and the associated concepts look like this:
The code of parser and calls of test engines look like this:
Finally, the call of parser in the test wrapping looks like this:
The whole code of the example is here - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/GUITests/Chapter0/
Actually, the test “script” for parser - FirstScript.script looks like this:
P.S. I’d like to tell to Delphi ANTAGONISTS – GUYS, this is not about Delphi. Generally speaking.
If you wish, I’ll write THE SAME on Obj-C or Python.
P.P.S. This post is IMHO the BEST I’ve ever written about “work”. If it is “not interesting” or “not understandable” – then, probably, I should stop “writing”. Probably, I can’t clearly make a point of “what I’d like to clear”. I am not able to run after Bagel or GunSmoker. I write about other things. About the APPROACH to developing… It’s a pity if you don’t understand.
P.P.P.S. I’m ready to bet... If some “of my posts” was written by “some” Fowler or Larman, they would say – “that’s a miracle! The truth is now clear for us..."
My respect to Bagel and Gunsmoker.
P.P.P.P.S. A good man suggested this theme is GREATLY related to Dependency Injection (https://en.wikipedia.org/wiki/Dependency_injection) and gave me the reference - http://docs.jboss.org/weld/reference/1.0.0/en-US/html/
NO DOUBT I will try to discover this theme.
P.P.P.P.P.S. By the way, it is most simple to do Dependency Injection on Objective-C or Python. There are no private methods (all methods are somehow available by name) and one can “override class to COPY method” instead of the whole class in general.
P.P.P.P.P.P.S. The example was compiled and tested on Delphi XE4 and Delphi XE5.
GUI-testing. Table of contents
Now I want to explain “what is written there” and “why we need it”.
Actually, I wanted to give an example of how to test designed classes on our own, without DUnit (http://18delphi.blogspot.ru/2013/03/dunit_29.html), mock's (http://18delphi.blogspot.ru/2013/10/gui.html) and other (http://18delphi.blogspot.ru/2013/04/blog-post_8791.html) “odd staff”. .
I have been doing it for LONG TIME till I have mastered DUnit (http://18delphi.blogspot.ru/2013/03/blog-post.html).
How can we “simply take our designed class and test it” (http://18delphi.blogspot.ru/2013/04/tdd-delphi.html).
It should be noted – I am not looking for a “silver bullet”. As well as “spherical cows in vacuum”.
It should be clear that what I speak about is UNIVERSAL SOLUTIONS. They do NOT exist.
Once again – do NOT exist. UNIVERSAL SOLUTIONS.
If you start to look for “the disadvantages of the approach” and say “these were all particular cases”, I will note – YES, THESE ARE “particular cases”.
The WHOLE TESTING is made up of PARTICULAR CASES.
Some partial cases are derived from system requirements, some – from library specifications, some – from the already found errors.
These partial cases – EITHER WAY cover the tested system.
Absolute is a MYTH!
As well as a “silver bullet”.
But. “ANY boss-eyed and half-working, but WRITTEN test is BETTER than a million of PERFECT but UNWRITTEN ones”.
I want you to understand it.
If you still do not understand, it means the time FOR YOU to test has not yet arrived.
Probably, you “sleep in peace” as it is. The errors in the code do not bother you.
I’ll continue.
ANY test (I speak of) – AT LEAST allows you to determine the “regression” (https://en.wikipedia.org/wiki/Regression_testing), i.e. – to control the fact that in the system you develop when you change the code – NOTHING (tested already) gets out of order.
So.
I’ll try to analyze it all using the example.
First, I will ASSUME ONE THING.
We’ll analyze the testing of the DETERMINED code (https://en.wikipedia.org/wiki/Deterministic_algorithm) . on its “generalized parameters” (including, say, Self).
Let’s define the methods we’ll work with:
The particular object to be tested is TscriptParser.
Let its specification be as follows:
TscriptParser is a tool to parse the input symbol stream, which has to possess the following properties:
1. It parses the input stream for “tokens” (a sequence of “not empty characters”).
2. A “token” cannot move “between lines”. As “end-of-line” indicator the characters with codes 13 and 10 or their combination serve.
3. The empty characters are “space”, “tabbing” and (basing on the previous example) – characters with codes 13 and 10.
4. The “class” of tokens – “strings enclosed in single quotes” - is singled out specially. These tokens are featured by the “single quotes at the beginning” and the “single quotes at the end”.
5. Let our requirements specification (RS) keep secret of “double quotes” and “doubled single quotes”.
6. There are “commentaries” – ANY text in the file line beginning from the substring “//” and up to the end of the current line has to be IGNORED. It does not have to appear in the list of “tokens”.
7. Token does not have to be empty. It means token has to include at least ONE “not empty character”.
Actually, the given RS determines the code of TscriptParser, which is given in the previous series. (Within the quotes.)
And now I’ll tell how TscriptParser is organised:
I’ll note straight – this is NOT an EXAMPLE of how Parsers SHOULD BE written.
Once again – NOT an EXAMPLE of how you SHOULD write Parsers.
Were I not bound to the example, I would have written the code in absolutely different way. Using libraries, with internal buffer, address arithmetic and lambdas during parsing for “tokens”.
This refers to the fact that you don’t have to be “too captious” to the parser code.
Now, let’s see how it is organized.
It is made of “a number of layers”:
- GetChar – reads the character appearing in the input stream and gives message about file end if we reach it.
- ReadLn – is based on GetChar; reads the current line of the input stream from the current position to the file end or the “end-of-line” indicator. It also “preprocesses” the read line for “line comments” and deleting them.
- NextToken – is based on ReadLn and parsers the current line for tokens in accordance with the rules described above.
Now, let’s get to testing.
Previously I have written about the concept of “checkpoints” - in first chapter .
Later we’ll consider it.
Now, we’ll define the concepts we need for testing:
1. Test engine (TtestEngine) – an “engine” to determine the state of the tested system.
2. Tested system – the code that we test using test engine (unfortunately, the tautology is inevitable). In our case the tested system is our class TscriptParser.
3. Experiment or test (TtestInstance) - a particular experiment of the tested system for FIXED input parameters. Let’s assume that experiments can’t be nested. It means, all code of tests we describe can carry out only ONE experiment at the interval of [t1..t2].
4. “Checkpoint” or “connection socket” (TtestSocket) – NAMED (identifiable, differentiated from the similar ones) object describing the special code fragment of the tested system.
5. “Measured value” (TtestValue) – a particular value taken from the “connection socket” at the moment.
6. Socket metric (TtestSocketMetric) – an array of “measured values” (TtestValue and TtestMetricValues) taken from the SPECIFIC socket during the WHOLE experiment.
Let us think that the methods of classes TscriptParser - GetChar, ReadLn and NextToken described above are the IMPORTANT spots of the tested system for taking readings using test engine.
I.e. these spots are the “checkpoints”.
How it looks in code:
The test engine and the associated concepts look like this:
unit Testing.Engine;
interface
{$IfNDef NoTesting}
uses
System.Classes,
Core.Obj
;
type
TtestSocket = record
{* - checkpoint }
private
rClass: TClass;
rMethod: String;
public
constructor Create(anObject: TObject; const aMethod: String);
{* - Creates a copy of checkpoint. }
function EQ(const anOther: TtestSocket): Boolean;
{* - Determines that checkpoints are the same }
end;//TtestSocket
TtestValueType = (test_vtChar, test_vtString);
{* - Possible types of the measured values }
TtestValue = record
{* - The measured value }
private
rType : TtestValueType;
{* - The type of the measured value. }
rAsString : AnsiString;
rAsChar : AnsiChar;
public
constructor CreateAsChar(aChar: AnsiChar);
constructor CreateAsString(const aString: AnsiString);
end;//TtestValue
TtestMetricValues = record
{* - The array of the values for metric. }
private
f_Stream : TStream;
{* The place to write the values of metrics. }
public
procedure Init(const aTestName: String; const aSocket: TtestSocket);
{* - Initializes the array of the values for metric }
procedure FlushAndClear;
{* - Writes the array to a durable medium and spares/clears it. }
procedure PutValue(const aValue: TtestValue);
{* - Writes the current value to the array of values for metrics }
end;//TtestMetricValues
TtestMetric = record
{* - The measured metrics. }
private
rSocket : TtestSocket;
{* - The checkpoint to take metrics }
rValues : TtestMetricValues;
{* - The current array of values taken from checkpoint }
public
constructor Create(const aTestName: String; const aSocket: TtestSocket);
function EQ(const anOther: TtestSocket): Boolean;
{* - Checks if metrics is taken from a specific checkpoint }
procedure FlushAndClear;
{* - Writes the array to a durable medium and spares/clears it. }
procedure PutValue(const aValue: TtestValue); overload;
{* - Writes the current value to the array of values for metrics }
procedure PutValue(const aValue: String); overload;
{* - Writes the current value to the array of values for metrics }
procedure PutValue(const aValue: AnsiChar); overload;
{* - Writes the current value to the array of values for metrics }
end;//TtestMetric
PtestMetric = ^TtestMetric;
TtestMetrics = array of TtestMetric;
{* - ALL metrics taken in the PARTICULAR experiment }
TtestInstance = class(TCoreObject)
{* - Experiment }
private
f_TestName : String;
f_Metrics : TtestMetrics;
protected
procedure Cleanup; override;
public
constructor Create(const aTestName: String);
function SocketMetric(const aSocket: TtestSocket): PtestMetric;
{* - Returns a copy of current metrics for the specific “checkpoint” to continue working with it }
end;//TtestInstance
TtestEngine = class
{* - Engine for testing }
public
class function StartTest(const aTestName: String): TtestInstance;
{* - Launches the test (experiment) }
class procedure StopTest;
{* - Completes the current experiment }
class function CurrentTest: TtestInstance;
{* - The currently carried out experiment }
end;
{$EndIf NoTesting}
implementation
{$IfNDef NoTesting}
uses
System.SysUtils
;
var
g_CurrentTest : TtestInstance = nil;
constructor TtestValue.CreateAsChar(aChar: AnsiChar);
begin
inherited;
rType := test_vtChar;
rAsChar := aChar;
end;
constructor TtestValue.CreateAsString(const aString: AnsiString);
begin
inherited;
rType := test_vtString;
rAsString := aString;
end;
constructor TtestSocket.Create(anObject: TObject; const aMethod: String);
begin
inherited;
rClass := anObject.ClassType;
rMethod := aMethod;
end;
function TtestSocket.EQ(const anOther: TtestSocket): Boolean;
{* - Determines that checkpoints are the same }
begin
Result := (Self.rClass = anOther.rClass) AND (Self.rMethod = anOther.rMethod);
end;
constructor TtestMetric.Create(const aTestName: String; const aSocket: TtestSocket);
begin
inherited;
rSocket := aSocket;
rValues.Init(aTestName, aSocket);
end;
function TtestMetric.EQ(const anOther: TtestSocket): Boolean;
{* - Checks if metrics is taken from the specific checkpoint }
begin
Result := Self.rSocket.EQ(anOther);
end;
procedure TtestMetric.FlushAndClear;
{* - Writes the array to a durable medium and spares/clears it. }
begin
Self.rValues.FlushAndClear;
end;
procedure TtestMetric.PutValue(const aValue: TtestValue);
{* - Writes the current value to the array of values for metrics }
begin
Self.rValues.PutValue(aValue);
end;
procedure TtestMetric.PutValue(const aValue: String);
{* - Writes the current value to the array of values for metrics }
begin
Self.rValues.PutValue(TtestValue.CreateAsString(aValue));
end;
procedure TtestMetric.PutValue(const aValue: AnsiChar);
{* - Writes the current value to the array of values for metrics }
begin
Self.rValues.PutValue(TtestValue.CreateAsChar(aValue));
end;
procedure TtestMetricValues.Init(const aTestName: String; const aSocket: TtestSocket);
{* - Initializes the array of values for metrics }
begin
f_Stream := TFileStream.Create(ExtractFilePath(ParamStr(0)) + '\' +
ExtractFileName(aTestName) +
'.' + aSocket.rClass.ClassName +
'.' + aSocket.rMethod +
'.out', fmCreate);
end;
procedure TtestMetricValues.FlushAndClear;
{* - Writes the array to a durable medium and spares/clears it. }
begin
FreeAndNil(f_Stream);
end;
procedure TtestMetricValues.PutValue(const aValue: TtestValue);
{* - Writes the current value to the array of values for metrics }
const
cEOL : AnsiString = #13#10;
begin
Assert(f_Stream <> nil, 'The file to write the values of metrics is not open');
case aValue.rType of
test_vtChar:
f_Stream.Write(@aValue.rAsChar, SizeOf(aValue.rAsChar));
test_vtString:
begin
f_Stream.Write(@aValue.rAsString[1], Length(aValue.rAsString));
f_Stream.Write(@cEOL[1], Length(cEOL));
end;//test_vtString
else
Assert(false, 'Unknown type of metrics values');
end;
end;
constructor TtestInstance.Create(const aTestName: String);
begin
Assert(g_CurrentTest = nil, 'Nested experiments are not supported');
f_TestName := aTestName;
inherited Create;
g_CurrentTest := Self;
end;
procedure TtestInstance.Cleanup;
var
l_Index : Integer;
begin
for l_Index := Low(f_Metrics) to High(f_Metrics) do
f_Metrics[l_Index].FlushAndClear;
Finalize(f_Metrics);
inherited;
end;
function TtestInstance.SocketMetric(const aSocket: TtestSocket): PtestMetric;
{* - Returns a copy of the current metrics for the specific “checkpoint” to continue working with it }
var
l_Index : Integer;
begin
for l_Index := Low(f_Metrics) to High(f_Metrics) do
if f_Metrics[l_Index].EQ(aSocket) then
begin
Result := @f_Metrics[l_Index];
Exit;
end;//f_Metrics[l_Index].EQ(aSocket)
SetLength(f_Metrics, Succ(Length(f_Metrics)));
f_Metrics[High(f_Metrics)] := TtestMetric.Create(f_TestName, aSocket);
Result := @f_Metrics[High(f_Metrics)];
end;
class function TtestEngine.StartTest(const aTestName: String): TtestInstance;
{* - Launches the test (experiment)}
begin
Result := TtestInstance.Create(aTestName);
end;
class procedure TtestEngine.StopTest;
{* - Completes the current experiment }
begin
Assert(g_CurrentTest <> nil, 'Something has gone wrong. No current experiment');
FreeAndNil(g_CurrentTest);
end;
class function TtestEngine.CurrentTest: TtestInstance;
{* - Currently carried out experiment}
begin
Result := g_CurrentTest;
end;
{$EndIf NoTesting}
end.
The code of parser and calls of test engines look like this:
unit Script.Parser;
interface
uses
Classes,
Core.Obj
;
{$IfNDef NoTesting}
{$Define TestParser}
{$EndIf NoTesting}
type
TscriptTokenType = (script_ttUnknown, script_ttToken, script_ttString);
TscriptParser = class(TCoreObject)
private
f_Stream : TStream;
f_EOF : Boolean;
f_CurrentLine : String;
f_PosInCurrentLine : Integer;
f_Token : String;
f_TokenType : TscriptTokenType;
protected
procedure Cleanup; override;
function ReadLn: String;
protected
function GetChar(out aChar: AnsiChar): Boolean;
public
constructor Create(const aStream : TStream); overload;
constructor Create(const aFileName : String); overload;
function EOF: Boolean;
procedure NextToken;
public
property TokenString: String
read f_Token;
end;//TscriptParser
implementation
uses
System.SysUtils
{$IfDef TestParser}
,
Testing.Engine
{$EndIf TestParser}
;
constructor TscriptParser.Create(const aStream : TStream);
begin
inherited Create;
f_PosInCurrentLine := 1;
f_EOF := false;
f_Stream := aStream;
end;
constructor TscriptParser.Create(const aFileName : String);
var
l_FileName : String;
begin
l_FileName := ExtractFilePath(ParamStr(0)) + '\' + aFileName;
Create(TFileStream.Create(l_FileName, fmOpenRead));
end;
procedure TscriptParser.Cleanup;
begin
FreeAndNil(f_Stream);
inherited;
end;
function TscriptParser.GetChar(out aChar: AnsiChar): Boolean;
begin
if (f_Stream.Read(aChar, SizeOf(aChar)) = SizeOf(aChar)) then
begin
Result := true;
{$IfDef TestParser}
TtestEngine.CurrentTest.SocketMetric(TtestSocket.Create(Self, 'GetChar')).PutValue(aChar);
// - we take readings of the current checkpoint
{$EndIf TestParser}
end
else
Result := false;
end;
function TscriptParser.ReadLn: String;
{$IfDef TestParser}
var
l_Result : AnsiString;
{$EndIf TestParser}
var
l_Char : AnsiChar;
l_Line : String;
l_LineCommentPos : Integer;
begin
{$IfDef TestParser}
try
{$EndIf TestParser}
try
l_Line := '';
while GetChar(l_Char) do
begin
if (l_Char = #13) then
begin
if GetChar(l_Char) then
begin
if (l_Char = #10) then
begin
Result := l_Line;
Exit;
end//l_Char = #10
else
Assert(false, 'Something has gone wrong, there’s no character 10 after 13');
end//GetChar(l_Char)
else
Assert(false, 'Something has gone wrong, at once the file end after character 13');
end;//l_Char = #13
l_Line := l_Line + l_Char;
end;//while GetChar(l_Char)
f_EOF := true;
Result := l_Line;
finally
l_LineCommentPos := Pos('//', Result);
if (l_LineCommentPos > 0) then
begin
Delete(Result, l_LineCommentPos, Length(Result) - l_LineCommentPos + 1);
end;//l_LineCommentPos > 0
end;//try..finally
{$IfDef TestParser}
finally
TtestEngine.CurrentTest.SocketMetric(TtestSocket.Create(Self, 'ReadLn')).PutValue(Result);
// - we take readings of the current checkpoint
end;//try..finally
{$EndIf TestParser}
end;
procedure TscriptParser.NextToken;
const
cQuote = #39;
cWhiteSpace = [#32,#9];
begin
f_TokenType := script_ttUnknown;
f_Token := '';
while true do
begin
if (f_PosInCurrentLine >= Length(f_CurrentLine)) then
begin
// - ALL current line kind of has been processed
f_CurrentLine := '';
f_PosInCurrentLine := 1;
end;//f_PosInCurrentLine > Length(f_CurrentLine)
while(f_CurrentLine = '') do
begin
f_CurrentLine := ReadLn;
if (f_CurrentLine = '') then
if f_EOF then
Exit;
end;//while(f_NextToken = '')
// Here we pass empty characters:
while (f_PosInCurrentLine <= Length(f_CurrentLine)) do
if (f_CurrentLine[f_PosInCurrentLine] in cWhiteSpace) then
Inc(f_PosInCurrentLine)
else
break;
if (f_PosInCurrentLine <= Length(f_CurrentLine)) then
break;
end;//while true
// Here we collect NOT empty characters:
if (f_CurrentLine[f_PosInCurrentLine] = cQuote) then
begin
f_TokenType := script_ttString;
Inc(f_PosInCurrentLine);
while (f_PosInCurrentLine <= Length(f_CurrentLine)) do
if (f_CurrentLine[f_PosInCurrentLine] <> cQuote) then
begin
f_Token := f_Token + f_CurrentLine[f_PosInCurrentLine];
Inc(f_PosInCurrentLine);
end//not (f_CurrentLine[f_PosInCurrentLine] in cWhiteSpace)
else
break;
end//f_CurrentLine[f_PosInCurrentLine] = ''
else
begin
f_TokenType := script_ttToken;
while (f_PosInCurrentLine <= Length(f_CurrentLine)) do
if (not (f_CurrentLine[f_PosInCurrentLine] in cWhiteSpace)) then
begin
f_Token := f_Token + f_CurrentLine[f_PosInCurrentLine];
Inc(f_PosInCurrentLine);
end//not (f_CurrentLine[f_PosInCurrentLine] in cWhiteSpace)
else
break;
end;//else
{$IfDef TestParser}
case f_TokenType of
script_ttString:
TtestEngine.CurrentTest.SocketMetric(TtestSocket.Create(Self, 'NextToken')).PutValue('Single quoted string:');
script_ttToken:
// - we do nothing
;
else
Assert(false, 'Something has gone wrong');
end;//case f_TokenType
TtestEngine.CurrentTest.SocketMetric(TtestSocket.Create(Self, 'NextToken')).PutValue(f_Token);
// - we take readings of the current checkpoint
{$EndIf TestParser}
//f_CurrentLine := '';
end;
function TscriptParser.EOF: Boolean;
begin
Result := f_EOF AND (f_CurrentLine = '');
end;
end.
Finally, the call of parser in the test wrapping looks like this:
var
l_Parser : TscriptParser;
begin
TtestEngine.StartTest(aFileName);
try
l_Parser := TscriptParser.Create(aFileName);
try
while not l_Parser.EOF do
l_Parser.NextToken;
finally
FreeAndNil(l_Parser);
end;//try..finally
finally
TtestEngine.StopTest;
end;//try..finally
end;
The whole code of the example is here - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/GUITests/Chapter0/
Actually, the test “script” for parser - FirstScript.script looks like this:
DoNothing // - for now we do nothing // - here we do nothing too for now DoNothing1 DoNothing2 // - here we do nothing too for now, but we check TWO tokens DoNothing3 DoNothing4 // - here we do nothing too for now, but we check TWO tokens with a spaces at the BEGINNING DoNothing5 DoNothing6 // - here we do nothing too for now, but we check TWO tokens without spaces in the END DoNothing7 // - here we do nothing too for now, but we check ONE token without spaces in the END DoNothing8 DoNothing9 DoNothing10 // - here we do nothing too for now, but we check THREE tokens DoNothing11 DoNothing12 DoNothing13 DoNothing14 // - here we do nothing too for now, but we check FOUR tokens // - we process a “purely empty line” 'aString1'// - we try to process a string 'aString2 with spaces'// - we try to process a string with spaces // - we process a “purely empty line”
P.S. I’d like to tell to Delphi ANTAGONISTS – GUYS, this is not about Delphi. Generally speaking.
If you wish, I’ll write THE SAME on Obj-C or Python.
P.P.S. This post is IMHO the BEST I’ve ever written about “work”. If it is “not interesting” or “not understandable” – then, probably, I should stop “writing”. Probably, I can’t clearly make a point of “what I’d like to clear”. I am not able to run after Bagel or GunSmoker. I write about other things. About the APPROACH to developing… It’s a pity if you don’t understand.
P.P.P.S. I’m ready to bet... If some “of my posts” was written by “some” Fowler or Larman, they would say – “that’s a miracle! The truth is now clear for us..."
My respect to Bagel and Gunsmoker.
P.P.P.P.S. A good man suggested this theme is GREATLY related to Dependency Injection (https://en.wikipedia.org/wiki/Dependency_injection) and gave me the reference - http://docs.jboss.org/weld/reference/1.0.0/en-US/html/
NO DOUBT I will try to discover this theme.
P.P.P.P.P.S. By the way, it is most simple to do Dependency Injection on Objective-C or Python. There are no private methods (all methods are somehow available by name) and one can “override class to COPY method” instead of the whole class in general.
P.P.P.P.P.P.S. The example was compiled and tested on Delphi XE4 and Delphi XE5.
Комментариев нет:
Отправить комментарий