Testing a PetitParser class
Once you have a parser defined as a class, it is a good idea to establish test cases for every single rule, both as regression tests, and as test cases for further development. We show, using the PetitParser SPL case study, how to do this.
Note that we have first fully developed a parser as a script and are now turning our scripted tests into examples. An alternative approach would have been to turn an early version of the scripted parser into a class, to enable TDD for further development. See also Example-driven development by example.
Testing grammar rules
In GT, test cases are implemented as Examples, that is, methods that perform assertions on an example object returned by the example method.
Defining the Example test class
To create test examples for a PetitParser class called
MyParser, you should define
MyParserExamples. This may be a subclass of
ProtoObject subclass: #Object
, but even better is to define it as a subclass of
Object subclass: #PP2CompositeNodeExamples
. This class provides several utility methods that allow one to test that a given input can be parsed by a particular rule, and will fail with other rules.
PP2CompositeNode subclass: #SPLGrammar
instanceVariableNames: 'keyword identifier boolean integer float number string varDecl primary unary factor term comparison equality logicAnd logicOr assignment expression exprStmt printStmt ifStmt whileStmt block statement declaration program parenthesizedExpression negatedUnary assignmentExpression comment ignorable'
parser class, we define
PP2CompositeNodeExamples subclass: #SPLGrammarExamples
You should also define a
parserClass method returning the class to be used to parse the examples. In our case it is
Defining test examples
Each test is an example method, so must be defined with a
The most basic tests just check that an input is parsed by a given rule, and then return the result.
For example, we test that the
integer rule works using the
parse: aString rule: aSymbol
^ self parse: aString rule: aSymbol end: aString size
integer42 <gtExample> ^ self parse: '42' rule: #integer
We might like to assert that a given input will be parsed by some rules and not others. For this we use
fail: aString rule: aSymbol
| production context result |
production := self parserInstanceFor: aSymbol.
context := self context.
result := production parse: aString withContext: context.
assert: (result isPetit2Failure or: [context atEnd not])
description: 'Able to parse ' , aString printString.
threeTimesFour <gtExample> | input | input := '3*4'. self parse: input rule: #expression. self fail: input rule: #unary. ^ self parse: input rule: #factor
Examples can also be composed. Most of the grammar tests have unique inputs, but here is an example of a composed example:
whileProgram <gtExample> | input | input := self whileProgramSource. self fail: input rule: #declaration. self fail: input rule: #statement. ^ self parse: input rule: #program