Для моего лексера ( токенизатор ) все 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).