基於Predictive Parsing的ABNF語法分析器(四)——準備單元測試代碼

單元測試的重要性是不言而喻的,對於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: [email protected])

    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: [email protected])

    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: [email protected])

    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 ”即後面多一個空格的情形,具體的原因我們在後面的帖子裏再慢慢分析吧。

本系列文章索引:基於預測的ABNF文法分析器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章