如何测试解释器或编译器?

13

我一直在尝试创建一个Brainfuck解释器,虽然创建和运行相当简单,但我想能够对其进行测试。然而我无法理解需要编写多少个测试才能测试所有可能的指令组合以确保实现正确性。

显然,Brainfuck的指令集很小,但我不禁思考随着更多指令的添加,您的测试代码将呈指数增长,比通常的测试更甚。

现在,就编写编译器和解释器而言,我是一个非常新手的人,所以我的假设可能完全错误。

基本上,像这样的测试从何处开始呢?

6个回答

11

测试编译器与测试其他应用程序略有不同,因为只要所有汇编代码版本都能正确执行,编译器产生的不同版本是可以接受的。但如果您只是测试解释器,则基本上与任何其他基于文本的应用程序相同。以下是Unix中心的观点:

  1. You will want to build up a regression test suite. Each test should have
    • Source code you will interpret, say test001.bf
    • Standard input to the program you will interpret, say test001.0
    • What you expect the interpreter to produce on standard output, say test001.1
    • What you expect the interpreter to produce on standard error, say test001.2 (you care about standard error because you want to test your interpreter's error messages)
  2. You will need a "run test" script that does something like the following

    function fail {
      echo "Unexpected differences on $1:"
      diff $2 $3
      exit 1
    }
    
    for testname
    do
      tmp1=$(tempfile)
      tmp2=$(tempfile)
      brainfuck $testname.bf < $testname.0 > $tmp1 2> $tmp2
      [ cmp -s $testname.1 $tmp1 ] || fail "stdout" $testname.1 $tmp1
      [ cmp -s $testname.2 $tmp2 ] || fail "stderr" $testname.2 $tmp2
    done
    
  3. You will find it helpful to have a "create test" script that does something like

    brainfuck $testname.bf < $testname.0 > $testname.1 2> $testname.2
    

    You run this only when you're totally confident that the interpreter works for that case.

  4. You keep your test suite under source control.

  5. It's convenient to embellish your test script so you can leave out files that are expected to be empty.

  6. Any time anything changes, you re-run all the tests. You probably also re-run them all nightly via a cron job.

  7. Finally, you want to add enough tests to get good test coverage of your compiler's source code. The quality of coverage tools varies widely, but GNU Gcov is an adequate coverage tool.

祝你的解释器好运!如果你想看一个精心制作但文档不太完善的测试基础设施,请查看Quick C--编译器test2目录。

编译器的一个经典测试用例是编译编译器本身,并检查编译后的编译器是否与原编译器相同。这被称为自举。 - Alexander Oh

2
我认为测试编译器没有什么“特殊”的地方;从某种意义上说,它比测试一些程序更容易,因为编译器有一个基本的高级概述——你提交源代码,它返回(可能)编译后的代码和(可能)一组诊断信息。
像任何复杂的软件实体一样,会有许多代码路径,但由于它都是以数据为导向的(输入文本,输出文本和字节),所以编写测试很简单。

2
我已经写了一篇关于编译器测试的文章,原来的结论(稍微缓和后才发表)是:重复造轮子是道德上错误的。 除非你已经了解了所有现有的解决方案并且有非常好的理由忽略它们,否则你应该从已经存在的工具入手。 最简单的起点是Gnu C Torture,但请记住,它基于Deja Gnu,存在一些问题。(即使是关于Hello World示例的关键错误报告,我也尝试了六次才让维护者允许在邮件列表上发布。)
我自负地建议您将以下内容作为调查工具的起点:
  1. 软件:实践与经验,2007年4月。(付费软件,不对大众公开——可在http://pobox.com/~flash/Practical_Testing_of_C99.pdf免费获取预印本。)

  2. http://en.wikipedia.org/wiki/Compiler_correctness#Testing(主要由我编写。)

  3. 编译器测试参考文献(如果有任何更新,请告知我。)


1
在Brainfuck的情况下,我认为应该使用Brainfuck脚本进行测试。 我会测试以下内容:
1:所有单元格都初始化为0吗?
2:当您将数据指针减小到当前指向第一个单元时会发生什么? 它是否回绕? 是否指向无效内存?
3:当您将数据指针增加到指向最后一个单元时会发生什么? 它是否回绕? 是否指向无效内存?
4:输出功能是否正常?
5:输入功能是否正常?
6:[ ]功能是否正常?
7:当您将字节递增超过255次时会发生什么? 它是否正确地回绕为0,或者被错误地视为整数或其他值。
还有更多的测试可能性,但这可能是我开始的地方。 几年前我写了一个BF编译器,那里有一些额外的测试。 特别是我通过在块内有大量代码来测试[ ],因为我的代码生成器的早期版本在那里存在问题(在x86上使用jxx时,如果块产生了128个字节左右的代码,就会出现无效的x86 asm)。

-1

你可以使用一些已经编写好的应用程序进行测试。


是的,我一直在做这个,它确实有些帮助,但我还是很好奇更广泛的情况。例如,如果我创建自己的编程语言,我就没有现成的应用程序可以直接使用了。 - Steven Raybell
你可以编写自己的测试;每当你发现一个漏洞,都可以编写一个测试来确保它不会再次出现。 - rmmh
噢,显然。 :-) 这就是为什么我问这个问题的原因。但是如果我们还处于 0 日,简单地将已编写的应用程序扔给它是不可能的。就是这样。编写测试倒是可以的。: D - Steven Raybell

-3

秘诀在于:

  • 分离关注点
  • 遵守 Demeter 法则
  • 注入依赖项

软件难以测试是开发人员像1985年一样编写的迹象。很抱歉这么说,但是使用我在这里介绍的三个原则,即使是有行号的BASIC也可以进行单元测试(因为您可以执行“goto variable”来注入依赖项)。


-1:楼主询问测试解释器和编译器的技术,告诉他应该写好代码并不能回答他的问题。 - Jørgen Fogh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接