Parsing 测试(自动)解析器的更好方法?

Parsing 测试(自动)解析器的更好方法?,parsing,testing,abstract-syntax-tree,Parsing,Testing,Abstract Syntax Tree,我最近正在编写一种小型编程语言,并且已经完成了它的解析器的编写。我想为解析器编写一个自动测试(其结果是一个抽象语法树),但我不确定哪种方法更好 首先,我尝试将AST序列化为S表达式文本,并将其与我手工编写的预期输出文本进行比较,但它存在一些问题: 序列化文本与预期输出(如空格)之间存在细微的无意义差异。例如,以下各项之间没有区别: (attribute (symbol str) (symbol length)) (已序列化)和: (那是我亲手写的)在它们的意义上,但是字符串比较当然会区分它们

我最近正在编写一种小型编程语言,并且已经完成了它的解析器的编写。我想为解析器编写一个自动测试(其结果是一个抽象语法树),但我不确定哪种方法更好

首先,我尝试将AST序列化为S表达式文本,并将其与我手工编写的预期输出文本进行比较,但它存在一些问题:

  • 序列化文本与预期输出(如空格)之间存在细微的无意义差异。例如,以下各项之间没有区别:

    (attribute (symbol str) (symbol length))
    
    (已序列化)和:

    (那是我亲手写的)在它们的意义上,但是字符串比较当然会区分它们。好的,我可以通过正常化来解决它

  • 当测试失败时,它不会简洁地显示实际树和预期树之间的差异。我只想显示一个差异节点,而不是整个树

其次,我尝试编写S-expression解析器,并将解析器(待测试)生成的AST与S-expression解析器(我刚刚实现的)从手写的预期输出生成的AST进行比较。然而,我意识到S-表达式也需要测试,它可能真的是胡说八道

我想知道测试解析器的典型且简单的方法是什么

另外,我使用的是Java,不希望与第三方库有任何依赖关系。

以下是我的想法。语法定义一种语言。语言是语法生成的字符串集,或语法分析器接受的字符串集

考虑到这一点,除了测试AST是否正确外,重要的是测试解析器是否接受该语言中的字符串,并拒绝您认为不应该属于它的字符串

从这个意义上说,一个简单的接受或拒绝(拒绝的输入位置的加分)就足以构建一个漂亮的大型测试用例集

示例:

()
(a)
((((((((((a))))))))))
((((((((((a)))))))))
(a (a (a (a (a (a (b)))))))
(((((((b) a) a) a) a) a) a)
(((((((b a) a) a) a) a) a)
((a)(a)(a)(a))
((a)(a a)(a))
(())
(()())
((()())(()())(()()))
((()())()()(()()))
...

如果您正在为解析器寻找一个完全自动化和可扩展的单元测试框架,我建议您采用以下方法:

输入错误 创建一组错误输入的样本。然后将它们分别输入到解析器中,确保解析器拒绝它们。我最好为每个定义预期输出的测试用例提供元数据——解析器应该产生的特定错误代码/消息

正确输入 与前一种情况一样,创建一组表示各种正确输入的样本。除了解析器接受所有输入的简单验证之外,还存在验证实际抽象语法树是否有意义的问题

为了解决这个问题,我将做以下工作:用一些众所周知的格式描述每个测试用例的预期AST,这些格式可以被认为没有bug的第三方解析器安全地解析(反序列化为实际的内存中AST结构)(针对您的情况)。自然的选择是XML,因为大多数语言/编程框架都支持XML,并提供相应的(反)序列化功能。最好的解决方案是直接反序列化为AST节点类型。由于存在方便的XML可视化编辑工具,因此构建大型测试用例也是可行的

然后,我将使用将两个AST配对并比较每对中的两个节点是否相等来构造一个AST比较器。但是,相等是一个每AST节点类型特定的操作


注:

  • 这种方法适用于大多数单元测试框架,如JUnit
  • AST到XML序列化是调试编译器的受欢迎工具
  • 访问者模式实现可以很容易地作为编译器中多个处理阶段的主干
  • 有一些编译器测试套件免费提供,可以为您的项目提供一些启发-例如,请参见Ada编程语言的示例,尽管此测试套件处理更高级别的测试,而不仅仅是解析器测试

您不能(当然是递归地)比较这些树吗?检查字符串比较似乎毫无意义,而且容易出错。通过一点额外的工作,它还可以给出差异开始的地方的提示。我尝试了第二种方法,所以我手工编写了预期的树(在S表达式中),还编写了一个S表达式解析器。将S表达式规范化为每行一个括号、原子或点如何?我认为,这是毫无意义的。。如果你不知道例程的确切输出,那么测试的目的是什么?将测试用例写入可观察、准确和/或验证的输出。如果解析器的工作不可预测,除非您正在计划某种TDD,否则不要在测试中乱来。正确解析的定义是接受属于该语言的字符串,拒绝不属于该语言的字符串。有些语言可以用简单的英语句子很好地定义;语法和该语法的解析器试图构建识别/生成该语言的机器。语法的抽象可以在解析过程中完成,但不必如此。谢谢你的建议,你救了我。当我编写了一个Visual Studio扩展,使我能够实时使用我的语言时,对我来说,这是事实上的测试。如果您添加可折叠区域,它还可以提供语法中预期的产品边界的上下文。想知道您的解析器是否在特定条件下挂起?实时解析器会很快告诉您何时会发生这种情况,因为它会立即终止实时编辑会话。这也是一个非常好的测试相对响应性/速度。我发现我的手工编写的解析器速度非常慢,并采取了一些措施加以改进,现在对于某些大小的正确性来说是可以接受的:一种方法是编写方法,从解析的AST中发出有效的语言字符串。如果您使用的是只考虑空白的精简版语言
()
(a)
((((((((((a))))))))))
((((((((((a)))))))))
(a (a (a (a (a (a (b)))))))
(((((((b) a) a) a) a) a) a)
(((((((b a) a) a) a) a) a)
((a)(a)(a)(a))
((a)(a a)(a))
(())
(()())
((()())(()())(()()))
((()())()()(()()))
...