我参与了一个项目,其中Java AST被翻译成另一种语言OpenCL,使用Eclipse编译器,并且有类似的问题.

我没有为你提供神奇的解决方案,但我会分享我的经验以防万一.

您使用预期输出(使用output.txt)进行测试的技术也是我的开始,但它成为测试的绝对维护噩梦.当我由于某种原因(发生几次)我不得不更改发生器或输出时,我不得不重写所有预期的输出文件 – 并且有大量的它们.我开始根本不想改变输出,因为害怕打破所有测试(这很糟糕),但最后我废弃了它们,而是对生成的AST进行了测试.这意味着我可以“松散地”测试输出.例如,如果我想测试if语句的生成,我可以在生成的类中找到唯一的if语句(我编写了帮助方法来完成所有这些常见的AST),验证一些关于它的事情,以及完成.该测试不关心如何命名类或是否有额外的注释或注释.由于测试更集中,因此最终工作得很好.缺点是测试与代码紧密耦合,所以如果我想撕掉Eclipse编译器/ AST库并使用其他东西,我需要重写我的所有测试.最后因为代码生成会随着时间的推移而改变,所以我愿意支付这个价格.

我也非常依赖集成测试 – 实际编译和运行目标语言生成的代码的测试.我认为这些类型的测试比单元测试更多,因为它们似乎更有用并且可以捕获更多问题.

至于访问者测试,我再次对它们进行更多集成式测试 – 获取一个非常小/特定的Java源文件,使用Eclipse编译器加载它,用它运行我的一个访问者并检查结果.在不调用Eclipse编译器的情况下测试的唯一另一种方法是模拟整个AST,这是不可行的 – 大多数访问者都是非平凡的并且需要完全构造/有效的Java AST,因为他们会从主类中读取注释.大多数访问者都是以这种方式测试的,因为他们要么生成小的OpenCL代码片段,要么建立一个单元测试可以验证的数据结构.

是的,我的所有测试都与Eclipse编译器紧密耦合.但我们正在撰写的实际软件也是如此.使用其他任何东西都意味着我们必须重写整个程序,所以这是我们很乐意支付的价格.我想没有一个解决方案 – 您需要权衡紧耦合的成本与测试可维护性/简单性.

我们还有相当数量的测试实用程序代码,例如使用默认设置设置Eclipse编译器,提取方法树的体节点的代码等.我们尽量保持测试尽可能小(我知道这是可能是常识但可能值得一提).

(以下对评论的回复中的编辑/添加 – 比评论回复更容易阅读/格式化)

“I also heavily rely on integration tests – tests that actually compile and run the generated code in the target language” What did these tests actually do? How are they different than the output.txt tests?

(再次编辑:重新阅读问题后,我意识到我们的方法是相同的,所以忽略这个)

而不是仅仅生成源代码并将其与我最初做的预期输出进行比较,集成测试生成OpenCL代码,编译并运行它.所有生成的代码都会生成输出,然后比较该输出.

例如,我有一个Java类,如果生成器正常工作,应该生成OpenCL代码,该代码将两个缓冲区中的值相加并将值放入第三个缓冲区.最初我会编写一个带有预期OpenCL代码的文本文件,并在我的测试中进行比较.现在,集成测试生成代码,通过OpenCL编译器运行它,运行它,然后测试检查值.

“As for visitor testing, again I do more integration-style testing with them – get a really small/specific Java source file, load it up with Eclipse compiler, run one of my visitors with it and check results. ” Do you mean run with one of your visitors, or run all the visitors up to the visitor you wanna test?

大多数访客可以彼此独立运行.在可能的情况下,我只会与我正在测试的访问者一起运行,或者如果依赖于其他访问者,则需要最少的访问者(通常只需要另外一个访问者).访问者不直接相互交谈,而是使用传递的上下文对象.这些可以在测试中人工构建,以使事物进入已知状态.

Other question, do you use mocks — at all, in this project? Moreover, do you regularly use mocks in other projects? I’m just trying to get a clear picture about the person I’m talking with 😛

在这个项目中,我们在大约5%的测试中使用模拟,甚至可能更少.我不会模拟任何Eclipse编译器的东西.

模拟的事情是我需要理解我正在嘲笑的东西,而Eclipse编译器则不然.有很多访问者方法被调用,有时我不确定应该调用哪一个(例如访问ExtendedStringLiteral或访问StringLiteral调用字符串文字?)如果我确实模拟了这个并假设一个或另一个,这可能与现实不符,即使测试通过,程序也会失败 – 不希望如此.我们做的唯一的嘲笑是注释处理器API,几个Eclipse编译器适配器和一些我们自己的核心类.

其他项目,如Java EE的东西,使用了更多的模拟,但我仍然不是他们的狂热用户. API的定义,理解和可预测性越高,我就越有可能考虑使用模拟.

The first phases of our program are just like of a regular compiler. We extract info from the source files and we fill up a (big and complex!) symbol table. How would you go about system testing this? In theory, I could create a test with the source files and also a symbolTable.txt (or .xml or whatever) that contains all the info about the symbolTable, but that would, I think, be a bit complex to do. Each one of those integration tests would be a complex thing to accomplish!

我试着采用一次性测试符号表的小位而不是整个批次的方法.如果我正在测试Java树是否正确构建,我会有类似的东西:

>仅针对if语句进行一次测试:

>使用包含一个if语句的一个方法获取源代码

>从此源构建符号表/树

>从主类中仅从方法体中拉出语句树(如果> 1或者没有找到方法体,发现类,方法体中的顶级语句节点,则进行失败测试)

>以编程方式比较if语句的节点属性(条件,正文)

>以类似的方式对每种其他类型的陈述进行至少一次测试.

>其他测试,可能是多个陈述等,或任何需要的

这种方法是集成式测试,但每次集成测试只测试系统的一小部分.

基本上我会尽量保持测试尽可能小.用于提取树的位的许多测试代码可以移动到实用程序方法中以使测试类保持较小.

I thought that maybe I could create a pretty printer that would take on the Symbol Table and output the correspondent source files (that, if everything was ok, would be just like the original source files). The problem is that the original files can have things in different order than what my pretty printer prints. I’m afraid that with this approach I might just be opening another can of worms. I’ve been relentless refactoring parts of the code and the bugs are starting to show off. I really need some integration tests to keep me on track.

这正是我采取的方法.但是在我的系统中,东西的顺序没有太大变化.我有基本上输出代码以响应Java AST节点的生成器,但是有一些自由,因为生成器可以递归地调用它们自己.例如,响应Java If语句AST节点而被触发的’if’生成器可以写出’if(‘,然后请求其他生成器呈现条件,然后写’){‘,请其他生成器写入出身体,然后写’}’.