SWI-Prolog - Модуль модульного тестирования библиотеки - Как используется опция forall? - PullRequest
0 голосов
/ 23 января 2019

Для моего лексера ( токенизатор ) все ASCII 7-битные символы (от 0x00 до 0x7F) имеют определенный токен. Поскольку SWI-Prolog поддерживает Unicode , коды символов изменяются от 0x0000 до 0xFFFF.

В моем лексере, поскольку есть много символов, которые не отображаются на определенный токен, существует неизвестный токен (tokUnknown).

Чтобы все символы с кодом от 0 до 127 (от 0x00 до 0x7F) не имели tokUnknown, необходимы тестовые случаи.

В тестовом примере требуется простой лексер для преобразования символа в токен.

tokenizer_unknown(Token) -->
    (
        white_space_char(W), !, white_space(W, S),
        { Token = tokWhitespace(S) }
    ;
        [S],
        { special_character(S,Token) }
    ;
        digit(D), !, number(D, N),
        { Token = tokNumber(N) }
    ;
        letter(L), !, word(L, W),
        { Token = tokWord(W) }
    ;
        [_],
        { Token = tokUnknown }
    ), !.

Вот тестовый пример для символа с кодом 0.

:- begin_tests(unknown).

test(001) :-
    Code = 0,
    char_code(Char,Code),
    Chars = [Char],
    phrase(tokenizer_unknown(Token),Chars,Rest),
    assertion(Rest == []),
    assertion(Token \== tokUnknown).

:- end_tests(unknown).

Для написания теста таким образом требуется 128 различных тестов для проверки tokUnknown.

Библиотека модульного тестирования SWI-Prolog plunit имеет опцию forall для генерации данных.

На основании документации тест должен выглядеть следующим образом:

test(002, [forall(???)]) :-
    char_code(Char,Code),
    Chars = [Char],
    phrase(tokenizer_unknown(Token),Chars,Rest),
    assertion(Rest == []),
    assertion(Token \== tokUnknown).

Можно ли использовать опцию forall, чтобы написать только один тестовый набор вместо 128 отдельных тестовых наборов для этой серии тестов?

Можете ли вы дать рабочую версию контрольного примера, используя forall?


Последующий

Шаблон для заполнения - forall(:Generator).

Когда я впервые увидел это, я был полностью сбит с толку и почти ушел, возвращаясь к написанию большого количества тестов, но застрял с этим, зная, насколько ценным и простым это должно быть для выполнения параметризованных тестов, например, JUnit 5 или NUnit 3 . Затем можно использовать параметризованный тест для фаззинга , а фаззинг можно усилить для генерации примеров счетчиков , например, QuickCheck , FsCheck


Пример 1

В жестко закодированном тесте

test(001) :-
    Code = 0,
    char_code(Char,Code),
    Chars = [Char],
    phrase(tokenizer_unknown(Token),Chars,Rest),
    assertion(Rest == []),
    assertion(Token \== tokUnknown).

Я хотел сделать Code переменной, которая менялась для каждого теста. Я также знал ограничения для Code, то есть от 0 до 127.

Таким образом, для этого простого генератора все, что было нужно, это предикат, который генерировал значения от 0 до 127 при вызове и возвращал их как переменную, например, Code.

между / 3 соответствует требованию, например,

?- between(0,3,Code).
Code = 0 ;
Code = 1 ;
Code = 2 ;
Code = 3.

Как видно из ответа, просто укажите предикат forall, например.

forall(between(0, 127, Code))

Пример 2

Этот тест должен проверить, что все отдельные пробельные символы или последовательность пробельных символов для 7-битных символов ASCII возвращаются как tokWhitespace и что пробельные символы являются строковым значением токена. ,

Обычай с токенами пробелов не состоит в том, чтобы включать символы в токен, но здесь они включены, потому что их легче удалить при необходимости, а затем удивляться, почему ОП не сделал этого. Так как это для обучения они включены.

Жестко закодированные тесты

:- begin_tests(white_space).

test(001) :-
    String = "\t",
    string_codes(String,Codes),
    phrase(whitespace(Tokens),Codes,Rest),
    assertion(Tokens == tokWhitespace("\t")),
    assertion(Rest == []).

test(011) :-
    String = "\t\r",
    string_codes(String,Codes),
    phrase(whitespace(Tokens),Codes,Rest),
    assertion(Tokens == tokWhitespace("\t\r")),
    assertion(Rest == []).

test(043) :-
    String = "\s\s\s",
    string_codes(String,Codes),
    phrase(whitespace(Tokens),Codes,Rest),
    assertion(Tokens == tokWhitespace("\s\s\s")),
    assertion(Rest == []).

:- end_tests(white_space).

В этом примере переменными являются String, например, "\t" и значение в токене tokWhitespace, например "\t".

Единичные пробельные символы:

?- code_type(Char,space).
Char = 9 ;        % tokHorizontalTab   \t
Char = 10 ;       % tokLineFeed        \n
Char = 11 ;       % tokVerticalTab     \v
Char = 12 ;       % tokFormFeed        \f
Char = 13 ;       % tokCarriageReturn  \r
Char = 32 ;       % tokSpace           \s
Char = 160 ;      % Yes, there are space characters defined beyond 7-bit ASCII. See: https://en.wikipedia.org/wiki/Whitespace_character#Unicode
Char = 5760 ; 
...

Один урок, извлеченный из десятилетий написания тестов на лексер / токенизацию, заключается в том, что каждый отдельный персонаж должен быть протестирован. Также тест не должен генерировать значения таким же образом, как проверка в лексере / токенизаторе. В этом случае тест не должен полагаться на code_type/2, потому что он используется в лексере / токенизаторе, а если code_type/2, где некоторые, как получить ошибку, тесты не обнаружат ее. Таким образом, контрольные примеры будут получать символы другими способами, в этом примере они будут из списка.

Второй урок, извлеченный из десятилетий тестирования рекурсивного кода, заключается в том, что тест должен тестировать как минимум на трех уровнях. В этом примере проверка пробельных символов будет проверять последовательности длиной до трех символов.

Третий урок состоит в том, что использование функциональной композиции с такими функциями, как комбинации, перестановки, конструкторы типов, деструкторы типов и т. Д. Уменьшает комбинаторный взрыв записи генераторов тестовых данных; наоборот, они способствуют комбинаторному взрыву тестовых случаев. Для этого в Прологе требуется перевод функциональных понятий в предикаты Пролога.

На основании этих уроков необходимы некоторые предикаты-помощники.

comb(0,_,[]).
comb(N,[X|T],[X|Comb]) :-
    N>0,
    N1 is N-1,
    comb(N1,T,Comb).
comb(N,[_|T],Comb) :-
    N>0,
    comb(N,T,Comb).

variation_string(N,L,String) :-
    between(1,N,N0),
    comb(N0,L,L1),
    permutation(L1,L2),
    string_chars(String,L2).

variation_number(N,L,String,Number) :-
    between(1,N,N0),
    comb(N0,L,L1),
    permutation(L1,L2),
    string_chars(String,L2),
    number_chars(Number,L2).

Пример использования:

?- variation_string(3,['\t','\r','\n'],String).
String = "\t" ;
String = "\r" ;
String = "\n" ;
String = "\t\r" ;
String = "\r\t" ;
String = "\t\n" ;
String = "\n\t" ;
String = "\r\n" ;
String = "\n\r" ;
String = "\t\r\n" ;
String = "\t\n\r" ;
String = "\r\t\n" ;
String = "\r\n\t" ;
String = "\n\t\r" ;
String = "\n\r\t" ;
false.

Для упрощения чтения forall создается предикат помощника.

generator_ascii_7bit_char_type_white(R) :-
    variation_string(3,['\t','\n','\v','\f','\r','\s'],R).

Теперь просто используйте генератор с forall в тесте.

:- begin_tests(white_space).

test(000, [forall(generator_ascii_7bit_char_type_white(String))]) :-
    string_codes(String,Codes),
    phrase(whitespace(Tokens),Codes,Rest),
    assertion(Tokens == tokWhitespace(String)),
    assertion(Rest == []).

:- end_tests(white_space).

С таким небольшим кодом все эти тесты были созданы и запущены (каждая точка представляет отдельный тестовый пример).

% PL-Unit: white_space ............................................................................................................................................................ done

Пример 3

В этом примере тестируется недетерминированный предикат , поэтому необходимо использовать findall . Это также имеет два входных параметра и два выходных параметра для предиката.

Подпись findall / 3 -

findall(+Template, :Goal, -Bag)

Чтобы использовать два значения с finall/3, Template не является кортежем, например (A,B), но список, например [A,B], а Bag представляет собой список списка, например, [["1",1],["2",2]], где каждый элемент в списке является результатом, а элементы во внутреннем списке являются значениями для соответствующих Template параметров.

Этот пример теста variation_number/4

:- begin_tests(variation_number_4).

variation_number_4(0,[],[]).
variation_number_4(1,[],[]).
variation_number_4(2,[],[]).
variation_number_4(3,[],[]).
variation_number_4(0,['1'],[]).
variation_number_4(1,['1'],[["1",1]]).
variation_number_4(2,['1'],[["1",1]]).
variation_number_4(3,['1'],[["1",1]]).
variation_number_4(0,['1','2'],[]).
variation_number_4(1,['1','2'],[["1",1],["2",2]]).
variation_number_4(2,['1','2'],[["1",1],["2",2],["12",12],["21",21]]).
variation_number_4(3,['1','2'],[["1",1],["2",2],["12",12],["21",21]]).
variation_number_4(0,['1','2','3'],[]).
variation_number_4(1,['1','2','3'],[["1",1],["2",2],["3",3]]).
variation_number_4(2,['1','2','3'],[["1",1],["2",2],["3",3],["12",12],["21",21],["13",13],["31",31],["23",23],["32",32]]).
variation_number_4(3,['1','2','3'],[["1",1],["2",2],["3",3],["12",12],["21",21],["13",13],["31",31],["23",23],["32",32],["123",123],["132",132],["213",213],["231",231],["312",312],["321",321]]).
variation_number_4(0,['1','2','3','4'],[]).
variation_number_4(1,['1','2','3','4'],[["1",1],["2",2],["3",3],["4",4]]).
variation_number_4(2,['1','2','3','4'],[["1",1],["2",2],["3",3],["4",4],["12",12],["21",21],["13",13],["31",31],["14",14],["41",41],["23",23],["32",32],["24",24],["42",42],["34",34],["43",43]]).
variation_number_4(3,['1','2','3','4'],[["1",1],["2",2],["3",3],["4",4],["12",12],["21",21],["13",13],["31",31],["14",14],["41",41],["23",23],["32",32],["24",24],["42",42],["34",34],["43",43],["123",123],["132",132],["213",213],["231",231],["312",312],["321",321],["124",124],["142",142],["214",214],["241",241],["412",412],["421",421],["134",134],["143",143],["314",314],["341",341],["413",413],["431",431],["234",234],["243",243],["324",324],["342",342],["423",423],["432",432]]).

test(000, forall(variation_number_4(Len,L,R0s))) :-
    findall([R,N],variation_number(Len,L,R,N),Rs),
    assertion(Rs == R0s).

:- end_tests(variation_number_4).

1 Ответ

0 голосов
/ 23 января 2019

Обратите внимание, что утверждения не верны. Они должны быть:

...
assertion(Rest == []),
assertion(Token \== tokUnknown).

В противном случае тест не обнаружит ошибку, которая вернет Rest или Token без привязки.

Что касается вашего вопроса для опции forall/1, я ожидаю, что сработает следующее (однако не пробовал):

test(002, [forall(between(0, 127, Code))]) :-
    char_code(Char, Code),
    phrase(tokenizer_unknown(Token), [Char], Rest),
    assertion(Rest == []),
    assertion(Token \== tokUnknown).
...