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

Testing 如何测试解释器或编译器?,testing,compiler-construction,interpreter,brainfuck,Testing,Compiler Construction,Interpreter,Brainfuck,我一直在尝试为Brainfuck创建一个解释器,虽然制作、启动和运行起来非常简单,但我的一部分人希望能够针对它运行测试。我似乎无法理解一个人可能需要编写多少测试来测试所有可能的指令组合,以确保实现是正确的 显然,使用Brainfuck,指令集很小,但我忍不住认为,随着更多指令的添加,您的测试代码将成倍增长。无论如何,这比你的典型测试更重要 现在,就编写编译器和解释器而言,我是一个新手,所以我的假设很可能偏离了基础 基本上,你从哪里开始测试这样的东西呢?你可以用一些已经编写好的应用程序进行测试。我

我一直在尝试为Brainfuck创建一个解释器,虽然制作、启动和运行起来非常简单,但我的一部分人希望能够针对它运行测试。我似乎无法理解一个人可能需要编写多少测试来测试所有可能的指令组合,以确保实现是正确的

显然,使用Brainfuck,指令集很小,但我忍不住认为,随着更多指令的添加,您的测试代码将成倍增长。无论如何,这比你的典型测试更重要

现在,就编写编译器和解释器而言,我是一个新手,所以我的假设很可能偏离了基础


基本上,你从哪里开始测试这样的东西呢?

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

我认为测试编译器没有什么“特殊”之处;从某种意义上说,这几乎比测试某些程序容易,因为编译器有这样一个基本的高级摘要——你交上源代码,它会返回(可能)编译后的代码和(可能)一组诊断消息


与任何复杂的软件实体一样,会有许多代码路径,但由于它们都非常面向数据(文本输入、文本和字节输出),编写测试非常简单。

对于brainfuck,我认为测试应该使用brainfuck脚本来完成。不过,我将测试以下内容:

1:是否所有单元格都初始化为0

2:当数据指针当前指向第一个单元格时,如果减少数据指针,会发生什么情况?包起来了吗?它是否指向无效内存

3:当数据指针指向最后一个单元格时,当数据指针递增时会发生什么情况?包起来了吗?它是否指向无效内存

4:输出功能正常吗

5:输入功能正确吗

6:[]的东西工作正常吗

7:当一个字节的增量超过255次时会发生什么情况,它是否正确地包装为0,或者它是否被错误地视为整数或其他值


更多的测试也是可能的,但这可能是我要开始的地方。几年前我写了一个BF编译器,它有一些额外的测试。特别是我通过在块中包含大量代码对[]进行了大量测试,因为早期版本的代码生成器存在问题(在使用jxx的x86上,当块生成的代码超过128字节左右时,我遇到问题,导致x86 asm无效)测试编译器与测试其他类型的应用程序略有不同,因为编译器可以生成程序的不同汇编代码版本,只要它们都正确。然而,如果您只是测试一个解释器,它与任何其他基于文本的应用程序几乎相同。以下是以Unix为中心的视图:

  • 您需要建立一个回归测试套件。每个测试都应该有
    • 您将解释的源代码,例如
      test001.bf
    • 您将解释的程序的标准输入,例如
      test001.0
    • 您希望解释器在标准输出上产生什么,比如
      test001.1
    • 您希望解释器在标准错误上产生什么,比如
      test001.2
      (您关心标准错误,因为您想测试解释器的错误消息)
  • 您将需要一个“运行测试”脚本,它执行以下操作

    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
    
    功能失败{
    echo“1美元上的意外差异:”
    差价2美元3美元
    出口1
    }
    对于testname
    做
    tmp1=$(临时文件)
    tmp2=$(临时文件)
    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
    完成
    
  • 您会发现有一个“createtest”脚本非常有用,它可以执行以下操作

    brainfuck $testname.bf < $testname.0 > $testname.1 2> $testname.2
    
    brainfuck$testname.bf<$testname.0>$testname.12>$testname.2
    
    只有当您完全确信解释器适用于该情况时,才能运行此命令

  • 您将测试套件置于源代码控制之下

  • 对测试脚本进行修饰很方便,这样就可以省去预期为空的文件

  • 任何时候有任何变化,您都会重新运行所有测试。您可能还通过cron作业整夜重新运行它们

  • 最后,您希望添加足够的测试以获得编译器源代码的良好测试覆盖率。覆盖率工具的质量差异很大,但它是一种适当的覆盖率工具


  • 祝你的翻译好运!如果你想看到一个精心设计但没有很好文档记录的测试基础设施,请查看
    test2
    目录中的。

    我写了一篇关于编译器测试的文章,其中的原始结论(稍微低调一点以便发布)是:重新发明轮子在道德上是错误的。除非你已经知道所有现有的解决方案,并且有很好的理由忽略它们,否则你应该先看看已经存在的工具。最容易开始的地方是,但请记住,它是基于Deja Gnu的,我们可以说,它有很多问题。(我甚至花了六次努力才让维护人员将一个关于Hello World的示例添加到邮件列表中。)

    我冒昧地建议您将以下内容作为工具调查的起点:

  • 。(付费软件,不向公众提供——免费预印本,网址为

  • (大部分是我写的。)

  • (请让我知道我错过的任何更新。)

  • 秘密在于:

    • 分离关注点
    • 遵守得墨忒尔定律
    • 注入依赖项
    好吧,很难测试的软件是开发者像1985年那样编写的一个标志。很抱歉这么说,但是利用三个原则