• 基于Predictive Parsing的ABNF语法分析器(4)——准备单元测试代码


    单元测试的重要性是不言而喻的,对于ABNF的例子来说,通过单元测试除了可以发现程序的BUG之外,还可以发现预测解析器能够支持哪些情况下的文法,以及那些情况下解析器无能为力(所谓FEATURE,嘿嘿)。

    我在这个项目中使用JUnit来做单元测试,先来看一段最简单的测试代码:

    /*
        This file is one of the component a Context-free Grammar Parser Generator,
        which accept a piece of text as the input, and generates a parser
        for the inputted context-free grammar.
        Copyright (C) 2013, Junbiao Pan (Email: panjunbiao@gmail.com)
    
        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        any later version.
    
        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
    
        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */
    
    //  SP             =  %x20
    	protected String SP() throws IOException, MatchException {
            assertMatch(is.peek(), 0x20);
            int value = is.read();
            return String.valueOf((char)value);
    	}
    
        @Test
        public void testSP() throws Exception {
            Tester<String> tester = new Tester<String>() {
                @Override
                public String test(AbnfParser parser) throws MatchException, IOException {
                    return parser.SP();
                }
            };
    //    测试用例1:字符串输入流为两个空格的情况(结果应该是只匹配第一个空格)
            Assert.assertEquals(String.valueOf((char)0x20), AbnfParserFactory.newInstance(new char[] {0x20, 0x20}).SP());
    //    测试用例2:字符串输入流为空的情况
            Assertion.assertMatchException("", tester, 1, 1);
    //    测试用例3:字符串输入流为回车(0x0D)的情况
            Assertion.assertMatchException("" + (char)0x0D, tester, 1, 1);
    
        }

    为图方便我把被测代码和测试代码都放到一起了,testSP()函数用来测试SP()函数的功能是否满足。SP()是用来解析空格输入的函数,即当输入流的下一个字符是空格时,返回这个空格,否则抛出一个匹配异常MatchException。

    单元测试的关键要素(输入参数)是测试用例(比如字符串输入流)和期望结果,在每一个测试用例中,都要调用一次被测函数,然后检查输出结果是否与预期结果一直。这里面有一部分代码是要重复使用的,为了偷懒(以免要大段大段的复制粘贴代码),抽象出Tester测试器接口和Assertion断言类。

    Tester测试器接口的代码如下:

    public interface Tester<E> {
        public abstract E test(AbnfParser parser) throws MatchException, IOException, CollisionException;
    }

    Tester接口只有一个test函数。例如在测试testSP函数中,定义了一个Tester的匿名类,在这个类的test函数中调用被测函数。有了Tester接口,我们在不同的测试用例下只需要指定一个Tester的实例就行,而不需要每次都调用被测函数。

    再来看看Assertion断言类,这也是一个提供方便的类,这个类提供了若干个方法,支持不同情况下调用Tester实例,并将测试结果与期望结果相比较,如果不一致则抛出异常。

    /*
        This file is one of the component a Context-free Grammar Parser Generator,
        which accept a piece of text as the input, and generates a parser
        for the inputted context-free grammar.
        Copyright (C) 2013, Junbiao Pan (Email: panjunbiao@gmail.com)
    
        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        any later version.
    
        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
    
        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */
    import junit.framework.Assert;
    import java.io.IOException;
    
    public class Assertion {
    //    断言匹配
        public static void assertMatch(String input, Tester tester, Object expectedOutput, int expectedPos, int expectedLine) throws MatchException, IOException, CollisionException {
    
    //    根据字符串输入流创建一个AbnfParser实例
            AbnfParser parser = AbnfParserFactory.newInstance(input);
    
    //    调用Tester接口实例进行测试
            Object output = tester.test(parser);
    
    //    判断测试结果是否与期望结果一致
            if (output == null && expectedOutput != null) Assert.fail();
            if (output != null && expectedOutput == null) Assert.fail();
            if (output != null && expectedOutput != null) Assert.assertEquals(expectedOutput, output);
    
    //    判断输入流的指针位置是否与期望一致
            Assert.assertEquals(expectedPos, parser.getInputStream().getPos());
            Assert.assertEquals(expectedLine, parser.getInputStream().getLine());
        }
    
        public static void assertMatch(String input, Tester tester, int expectedPos, int expectedLine) throws MatchException, IOException, CollisionException {
            AbnfParser parser = AbnfParserFactory.newInstance(input);
            tester.test(parser);
            Assert.assertEquals(expectedPos, parser.getInputStream().getPos());
            Assert.assertEquals(expectedLine, parser.getInputStream().getLine());
        }
    
    //    断言要抛出匹配异常
        public static void assertMatchException(String input, Tester tester, int expectedPos, int expectedLine) {
    //    根据字符串输入流创建一个AbnfParser实例
            AbnfParser parser = AbnfParserFactory.newInstance(input);
            try {
    //    调用测试函数
                tester.test(parser);
    //    如果执行到这里(意味着测试函数没有抛出异常),则测试不通过
                Assert.fail();
            } catch (MatchException me) {
    //    如果捕捉到匹配异常,则继续检查输入流的指针位置是否正确
                Assert.assertEquals(expectedPos, parser.getInputStream().getPos());
                Assert.assertEquals(expectedLine, parser.getInputStream().getLine());
            } catch (IOException e) {
    //    其他异常表明测试不通过
                Assert.fail();
            } catch (CollisionException ce) {
    //    其他异常表明测试不通过
                Assert.fail();
            }
        }
    
    //    断言要抛出冲突异常(流程类似匹配异常,主要针对ABNF规则名重名的情况)
        public static void assertCollisionException(String input, Tester tester, int expectedPos, int expectedLine) {
            AbnfParser parser = AbnfParserFactory.newInstance(input);
            try {
                tester.test(parser);
                Assert.fail();
            } catch (MatchException me) {
                Assert.fail();
            } catch (IOException e) {
                Assert.fail();
            } catch (CollisionException ce) {
                Assert.assertEquals(expectedPos, parser.getInputStream().getPos());
                Assert.assertEquals(expectedLine, parser.getInputStream().getLine());
            }
        }
    }

    这里再补充说明一下AbnfParserFactory工厂类,这个类主要是为了方便将字符串转换为输入流并创建AbnfParser实例而写的,代码很简单:

    /*
        This file is one of the component a Context-free Grammar Parser Generator,
        which accept a piece of text as the input, and generates a parser
        for the inputted context-free grammar.
        Copyright (C) 2013, Junbiao Pan (Email: panjunbiao@gmail.com)
    
        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        any later version.
    
        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
    
        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */
    
    import java.io.ByteArrayInputStream;
    
    public class AbnfParserFactory {
        public static AbnfParser newInstance(String prefix, String input) {
            return new AbnfParser(prefix, new ByteArrayInputStream(input.getBytes()));
        }
        public static AbnfParser newInstance(String input) {
            return new AbnfParser("", new ByteArrayInputStream(input.getBytes()));
        }
        public static AbnfParser newInstance(String prefix, char[] input) {
            return new AbnfParser(prefix, new ByteArrayInputStream(String.valueOf(input).getBytes()));
        }
        public static AbnfParser newInstance(char[] input) {
            return new AbnfParser("", new ByteArrayInputStream(String.valueOf(input).getBytes()));
        }
    }

    有了这个工厂类,单元测试的时候就方便多了,因为我们的测试用例可能是字符串、字符数组,这个工厂类能够帮助我们快速创建AbnfParser实例。

    通过单元测试可以发现很多我们意想不到的情况,例如:

        //		        elements       =  alternation *c-wsp
        @Test
        public void testElements() throws Exception {
            Tester<Elements> tester = new Tester<Elements>() {
                @Override
                public Elements test(AbnfParser parser) throws MatchException, IOException {
                    return parser.elements();
                }
            };
    
            String input;
            input = "A/B/C";
            Assertion.assertMatch(input, tester, AbnfParserFactory.newInstance(input).elements(), 6, 1);
    
    //        TODO
            input = "A/B/C  ";
            Assertion.assertMatchException(input, tester, 8, 1);
    
        }

    原来这个预测解析器无法处理对于输入的Alternation为“A/B/C ”即后面多一个空格的情形,具体的原因我们在后面的帖子里再慢慢分析吧。

  • 相关阅读:
    HTTP协议入门
    TCP/IP的分层管理
    TCP与UDP
    如何处理某个Web页面的HTTP请求
    AGC005D ~K Perm Counting
    “玲珑杯” 线上赛Round #17 B 震惊,99%+的中国人都会算错的问题
    bzoj4455 [Zjoi2016]小星星
    AGC010F Tree Game
    AGC016E Poor Turkeys
    AGC003E Sequential operations on Sequence
  • 原文地址:https://www.cnblogs.com/dyllove98/p/3124982.html
Copyright © 2020-2023  润新知