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.
Комментариев нет:
Отправить комментарий