2011-02-07 148 views
16

如何使用Make实现简单的回归测试框架? (我使用了GNU make,如果该事项。)执行`make check`或`make test`

我现在的makefile文件看起来像这样(编辑为简单起见):

OBJS = jscheme.o utility.o model.o read.o eval.o print.o 

%.o : %.c jscheme.h 
    gcc -c -o [email protected] $< 

jscheme : $(OBJS) 
    gcc -o [email protected] $(OBJS) 

.PHONY : clean 

clean : 
    -rm -f jscheme $(OBJS) 

我想有一套回归测试,例如expr.in测试“好”表达式& unrecognized.in测试“坏”一个,其中expr.cmp & unrecognized.cmp是每个的预期输出。手动测试是这样的:

$ jscheme <expr.in> expr.out 2>&1 
$ jscheme <unrecognized.in> unrecognized.out 2>&1 
$ diff -q expr.out expr.cmp # identical 
$ diff -q unrecognized.out unrecognized.cmp 
Files unrecognized.out and unrecognized.cmp differ 

我认为一组规则添加到Makefile看起来像这样:

TESTS = expr.test unrecognized.test 

.PHONY test $(TESTS) 

test : $(TESTS) 

%.test : jscheme %.in %.cmp 
    jscheme <[something.in]> [something.out] 2>&1 
    diff -q [something.out] [something.cmp] 

我的问题:
•我把什么的[东西]占位符?
•是否有方法将消息从diff替换为消息说“测试expr失败”?

+0

从哪里来的所有临时文件? – reinierpost 2011-02-08 08:47:08

+0

@reinierpost有什么问题:如果你有更好的方式来进行这些比较,通过一切手段发布一个包含它们的答案 - 这正是我所要求的那种帮助。 – 2011-02-08 15:01:55

+0

对不起,我以为我取消了这个问题。你确实需要临时文件。 – reinierpost 2011-02-08 15:26:14

回答

8

正如问题所述,您的原始方法是最好的。您的每个测试都是一对预期的输入和输出。 Make非常有能力迭代这些并运行测试;没有必要使用一个外壳for循环。事实上,通过这样做,您将失去并行运行测试的机会,并为自己创建额外的工作来清理临时文件(这是不需要的)。

这里有一个解决方案(使用bc为例):

SHELL := /bin/bash 

all-tests := $(addsuffix .test, $(basename $(wildcard *.test-in))) 

.PHONY : test all %.test 

BC := /usr/bin/bc 

test : $(all-tests) 

%.test : %.test-in %.test-cmp $(BC) 
    @$(BC) <$< 2>&1 | diff -q $(word 2, $?) - >/dev/null || \ 
    (echo "Test [email protected] failed" && exit 1) 

all : test 
    @echo "Success, all tests passed." 

解决方案直接解决了原来的问题:

  • 你要找的占位符$<$(word 2, $?)对应先决条件分别为%.test-in%.test-cmp。与@reinierpost相反,评论临时文件是不需要的。
  • 该差异消息是隐藏的,并使用echo替换。
  • 使用make -k调用makefile即可运行所有测试,无论单个测试是失败还是成功。
  • make -k all只有在所有测试成功时才会运行。

通过利用文件命名约定(*.test-in)定义all-tests变量时,我们避免手动列举每个测试和GNU使functions for file names。作为奖励,这意味着解决方案可扩展到数万个开箱即用的测试,因为GNU make中的变量长度为unlimited。这比基于shell的解决方案要好,一旦你碰到操作系统command line limit就会崩溃。

9

做一个测试运行脚本,需要一个测试名称并推断输入文件名,输出文件名和smaple数据来自:

#!/bin/bash 
set -e 
jscheme < $1.in > $1.out 2>&1 
diff -q $1.out $1.cmp 

然后,在你Makefile

TESTS := expr unrecognised 

.PHONY: test 
test: 
    for test in $(TESTS); do bash test-runner.sh $$test || exit 1; done 

你可以也尝试实施类似automakesimple test framework

+1

我喜欢我最初的独立目标计划的外壳循环思路。 shell脚本并不是一个坏主意,但我想我会将整个事物合并到makefile中。 – 2011-02-08 15:08:17

+1

另外我真的不想打开整个autotools蠕虫的罐头。 – 2011-02-08 15:09:37

2

我会解决你的问题差异。你可以这样做:

 
diff file1 file2 > /dev/null || echo Test blah blah failed >&2 

虽然你可能想使用CMP,而不是差异的。

另一方面,你可能会发现它有助于继续,并采取 暴跌和使用automake。你的Makefile.am(其全部) 看起来像:

 
bin_PROGRAMS = jscheme 
jscheme_SOURCES = jscheme.c utility.c model.c read.c eval.c print.c jscheme.h 
TESTS = test-script 

,你会得到一大堆的非常好的指标自由,包括一个漂亮的全功能测试框架。

2

我最终什么了这个样子的:

TESTS = whitespace list boolean character \ 
    literal fixnum string symbol quote 

.PHONY: clean test 

test: $(JSCHEME) 
    for t in $(TESTS); do \ 
     $(JSCHEME) < test/$$t.ss > test/$$t.out 2>&1; \ 
     diff test/$$t.out test/$$t.cmp > /dev/null || \ 
      echo Test $$t failed >&2; \ 
    done 

它是基于杰克凯利的想法,与包括乔纳森·莱弗勒的小费。