如何最好地为解析器编写单元测试用例?

8

我正在编写一个解析器,为每个命令生成32位操作码。例如,对于以下语句:

set lcl_var = 2

我的解析器会生成以下操作码:

// load immdshort 2 (loads the value 2)
0x10000010
// strlocal lclvar (lcl_var is converted to an index to identify the var)
0x01000002

请注意,lcl_var可以是任何变量。我该如何编写针对此的单元测试用例?我们能否避免硬编码数值?有办法使其通用化吗?

1
硬编码是最好的,单元测试应该非常明确地告诉您代码库中错误的具体位置。如果它是通用的,错误可能在“有效代码列表”而不是解析器中。 - Paxic
7个回答

2
这取决于你如何构建你的解析器。单元测试是测试单个单元的测试方法。
因此,如果您想将整个解析器作为一个单元进行测试,可以给它一个命令列表,并验证它产生正确的操作码(在编写测试时手动检查)。您可以为每个命令编写测试,并测试正常使用、边缘情况使用和超出边缘情况使用。例如,测试:
set lcl_var = 2
结果应该是:
0x10000010 0x01000002
并且对于0、-1、MAX_INT-1、MAX_INT+1等不同变量也是相同的。
您知道这些值的正确结果。对于不同的变量也是一样的。

2
如果你的问题是“如何在不为每个输入输出组合编写一个xUnit测试的情况下运行相同的测试?” 那么答案就是使用类似RowTest NUnit扩展的东西。我最近在我的博客上写了一篇快速启动文章。一个例子是:
[TestFixture]
    public class TestExpression
    {
        [RowTest]
        [Row(" 2 + 3 ", "2 3 +")]
        [Row(" 2 + (30 + 50 ) ", "2 30 50 + +")]
        [Row("  ( (10+20) + 30 ) * 20-8/4 ", "10 20 + 30 + 20 * 8 4 / -")]
        [Row("0-12000-(16*4)-20", "0 12000 - 16 4 * - 20 -")]
        public void TestConvertInfixToPostfix(string sInfixExpr, string sExpectedPostfixExpr)
        {
            Expression converter = new Expression();
            List<object> postfixExpr = converter.ConvertInfixToPostfix(sInfixExpr);

            StringBuilder sb = new StringBuilder();
            foreach(object term in postfixExpr)
            {
                sb.AppendFormat("{0} ", term.ToString());
            }
            Assert.AreEqual(sExpectedPostfixExpr, sb.ToString().Trim());
        }

1
int[] opcodes = Parser.GetOpcodes("set lcl_var = 2");
Assert.AreEqual(2, opcodes.Length);
Assert.AreEqual(0x10000010, opcodes[0]);
Assert.AreEqual(0x01000002, opcodes[1]);

0

不清楚您是在寻找方法论还是具体技术用于测试。

就方法论而言,也许您不想进行详细的单元测试。也许更好的方法是使用特定领域语言编写一些程序,然后执行操作码以产生结果,这样测试程序将检查此结果。通过这种方式,您可以运行一堆代码,但仅在最后检查一个结果。从简单的开始,排除明显的错误和难点。与每次检查生成的操作码相比,这将节省时间。

另一个方法是自动生成特定领域语言的程序以及期望的操作码。这可以非常简单,例如编写一个 Perl 脚本来生成一组程序:

set lcl_var = 2

set lcl_var = 3

一旦您拥有了特定领域语言的测试程序套件并且输出正确,您可以向后生成单元测试以检查每个操作码。由于您已经拥有了操作码,因此变成了检查解析器输出的正确性;审查其代码。

尽管我没有使用过 cppunit,但我使用过类似 cppunit 的内部工具。很容易使用 cppunit 实现单元测试。


0

你没有说明你用什么语言编写解析器,所以我假设你正在使用面向对象的语言。

如果是这样,那么依赖注入可以帮助你。如果发出的操作码的目标是类的一个实例(比如File),试着给你的发射器类一个构造函数,它接受一个该类型的对象作为发出代码的目标。然后,在一个单元测试中,你可以传入一个模仿目标类子类的模拟对象,捕获特定语句的发出的操作码,并断言它们是否正确。

如果你的目标类不容易扩展,你可能想创建一个基于它的接口,让目标类和模拟类都能实现。


我正在使用C++编写解析器。 - Vinay

0

据我理解,您首先需要为您的特定示例编写一个测试,即输入到您的解析器中的内容:

set lcl_var = 2

输出为:

0x10000010 // load immdshort 2
0x01000002 // strlocal lclvar 

当您已经实现了生产代码以通过该测试并进行了重构后,如果您对其无法处理任何本地变量感到不满意,请使用另一个不同的本地变量编写另一个测试,并查看它是否通过。例如,使用以下输入的新测试:
set lcl_var2 = 2

编写新的测试用例,以期望您想要的不同输出。一直重复此过程,直到您满意为止,确保您的生产代码足够健壮。


0

你想要测试什么?你想知道是否已创建了正确的“存储”指令?是否选择了正确的变量?要想清楚自己想要测试什么,这样测试就会很明显。只要不知道自己想要实现什么,就不知道如何去测试未知的事物。

与此同时,写一个简单的测试。明天或以后的某一天,当有东西出问题时你会再次来到这里。那时,你会更多地了解自己想要做什么,这可能更容易设计测试。

今天,不要试图成为你明天的样子。


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