对于我的令牌器,所有的ASCII 7位字符(0x00到0x7F)都有一个特定的令牌。作为SWI 支座 Unicode,字符代码从0x0000到0xFFFF。
在我的lexer中,由于有许多字符不能映射到特定的令牌,所以有一个未知令牌(tokUnknown)。
为了确保代码介于0到127 (0x00到0x7F)之间的所有字符都没有tokUnknown,需要测试用例。
测试用例需要一个简单的lexer来将字符转换为令牌。
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。
Prolog单元测试库折叠式有一个选项为所有来生成数据.
根据文档,测试应该如下所示:
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
此测试的目的是检查ASCII 7位字符的所有单独白空间字符或空格字符序列是否以tokWhitespace形式返回,并且空格字符是令牌的字符串值。
使用空格标记的自定义不是将字符包含在令牌中,而是在这里包含字符,因为如果需要的话,更容易删除它们,然后奇怪为什么OP没有这样做。因为这是为了学习,他们包括在内。
硬编码测试
:- 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 ;
...从几十年的词汇/标记化测试中学到的一个教训是,每个字符都需要测试。此外,测试不应该以与在lexer/tokenizer中的检查相同的方式生成值。在这种情况下,测试不应该依赖于code_type/2,因为这是在lexer/令牌程序中使用的,如果code_type/2 (有些人如何获得bug ),测试将不会检测到它。因此,测试用例将通过不同的方式获得字符,在本例中,它们将来自列表。
从几十年的递归代码测试中获得的第二个教训是,测试至少需要三个层次的测试。在这个例子中,空格字符的测试将测试最多三个字符的序列。
第三个教训是,使用函数组合(如组合、排列、类型构造函数和类型析构函数等)可以减少编写测试数据生成器的组合爆炸;相反,它们会导致测试用例的组合爆炸。要在Prolog中做到这一点,需要将功能概念转换为Prolog谓词。
根据这些经验教训,需要一些辅助谓词。
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/3的签名是
findall(+Template, :Goal, -Bag)要在finall/3中使用两个值,Template不是元组(例如(A,B) ),而是一个列表(例如[A,B] ),Bag是列表列表的列表,例如,列表中的每个项都是结果,而内部列表中的项是对应的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).发布于 2019-01-23 20:18:01
注意,断言是不正确的。它们应该是:
...
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).https://stackoverflow.com/questions/54334567
复制相似问题