TestExpression: Base class for all tests.
The test function returns a boolean and a comment.
If the test function returns True/False, the answer is good/bad and
the processing stops. If it returns None, then the next test
is computed.NumberOfIs: The number of time the first parameter string is found in the
student answer must be equal to the integer.
# Note the negation of the condition: ~
Bad(Comment(~NumberOfIs("x", 3) | ~NumberOfIs("y", 2),
"Your answer must contain 3 'x' and 2 'y'"))
TestDictionary: Base class for enumeration tests.
The class is easely derivable by specifying a dictionnary containing
all the allowed answers and how they are canonized.YesNo: Base class for yes/no tests.No: Returns True if the student answer No
Good(No())
Yes: Returns True if the student answer Yes
Good(Yes())
TestFunction: The parameter function returns a boolean and a comment.
The test returns True if the function returns True.
def not_even(answer, state):
'''This function should catch ValueError exception'''
if int(answer) % 2 == 0:
return False, ''
return True, 'The answer is an even integer.'
Bad(TestFunction(not_even))
TestInt: Base class for integer tests.Int: Returns True if the student answer is an integer equal
to the specified value.
# If the answer is not an integer a comment is returned.
Good(Int(1984) | Int(2001))
IntGT: Returns True if the student answer is an integer strictly greater than
the specified value.
# If the answer is not an integer a comment is returned.
Good(IntGT(1984) & IntLT(2001))
IntLT: Returns True if the student answer is an integer smaller than
the specified value.
# If the answer is not an integer a comment is returned.
Good(IntGT(1984) & IntLT(2001))
Length: Returns True if the length of the student answer is equal
to the integer specified.
# Note the negation of the condition: ~
Bad(Comment(~Length(3), "The expected answer is 3 character long"))
LengthLT: Returns True if the length of the student answer is smaller then
the integer specified.
# Note the negation of the condition: ~
Bad(Comment(~LengthLT(10),
"The expected answer is less than 10 characters long"))
TestNAry: Base class for tests with a variable number of test as arguments.And: True if all the children test returns True,
the syntax with '&' operator can be used.
As in other programmation language, by default, the evaluation stops
is the result is predictible.
Good(And(Contain('a'), Contain('b'), Contain('c')))
Good(Contain('a') & Contain('b') & Contain('c'))
# To continue the test even if the first is False, use shortcut=False.
# The following test returns a comment on 'b' answer even
# if the answer does not contains 'a'
Good(And(Comment(Contain('a'), 'comment A'),
Comment(Contain('b'), 'comment B'),
shortcut=False
))
Or: True if one of the child test returns True,
the syntax with '|' operator can be used.
As in other programmation language, by default, the evaluation stops
is the result is predictible.
Good(Or(Equal('a'), Equal('b'), Equal('c')))
Good(Equal('a') | Equal('b') | Equal('c'))
# To continue the test even if the first is True, use shortcut=False.
# The following test returns 2 comments on 'ab' answer and not
# only the first one.
Bad(Or(Comment(Contain('a'), 'comment A'),
Comment(Contain('b'), 'comment B'),
shortcut=False
))
TestString: Base class for tests with a string argument.
By default, the string in parameter is canonized.
In rare case, it is not the desired behavior, so we reject
the canonization:
# The Contain parameter must not be canonized because
# the constant string is yet a fragment of a canonized shell command.
Bad(Shell(Contain('<parameter>-z</parameter>', canonize=False)))
Contain: Returns True the student answer contains the string in parameter.
Good(Contain('python'))
Bad(Contain('C++'))
Good(Contain('')) # This test is always True
End: Returns True the student answer ends by the string in parameter.
Good(Comment(End('$'),
"Yes, the shell prompt is terminated by a $."
)
)
Bad(Comment(~ End('.'),
"An english sentence terminates by a dot."
)
)
Equal: Returns True the student answer is equal to the string in parameter.
Bad(Comment(Equal('A bad answer'),
"Your answer is bad because..."
)
)
Good(Equal('A good answer'))
Expect: Returns False if the student answer does not contains
the string in parameter, if a comment is not provided then
an automatic one is created.
It is a shortcut for: Bad(Comment(~Contain(string), "string is expected"))
Expect("foo")
Expect("bar", "You missed a 3 letters word always with 'foo'")
Reject: Returns False the student answer contains
the string in parameter, if a comment is not provided then
an automatic one is created.
It is a shortcut for: Bad(Comment(Contain(string), "string is unexpected"))
Reject("foo")
Reject("bar", "Why 'bar'? there is no 'foo'...")
Start: Returns True the student answer starts by the string in parameter.
Good(Start("3.141"))
Bad(Comment(~ Start('1'),
"The first digit is one"
)
)
TestUnary: Base class for tests with one child test.Bad: If the child test returns True then the student answer is bad.
Bad(Equal('5') | Contain('6'))
Bad(~ Equal('AA') & Equal('AA') ) # This one will never be bad...
# The next test will be bad if the answer contains 'x' and 'y'
# Beware of the And evaluation shortcut, the second test will not
# be evaluated if the answer does not contains 'x'
# The comments for the student are:
# x : not bad, but with comment 'x'
# y : not bad, without comment
# xy : bad with comments 'x' and 'y' concatened,
Bad( Comment(Contain('x'),'x') & Comment(Contain('y'),'y')) )
# The next test will be bad if the answer contains 'x' or 'y'
# Beware of the And evaluation shortcut, the second test will not
# be evaluated if the answer does contains 'x'
# The comments for the student are:
# x : bad with comment 'x'
# y : bad with comment 'y'
# xy : bad with comment 'x'
Bad( Comment(Contain('x'),'x') | Comment(Contain('y'),'y')) )
Comment: If the child test returns True then the comment will be displayed
to the student.
If the child test is yet commented, then the comment will be concatened.
Good(Comment(Equal('a'), "'a' is a fine answer"))
Bad(Comment(Equal('b') | Equal('c'), "Your answer is very bad"))
# Let know the student it did not fall in a trap :
Bad(Comment(~ Comment(~Equal('x'),
"Good ! You didn't answer 'x'"
),
"'x' is a very bad answer..."
)
)
# The same thing in another way with 2 successive tests:
Bad(Comment(Equal('x'), "'x' is a very bad answer...'")
Comment("Good ! You didn't answer 'x'")
# The following test will be no good nor bad but if it is evaluated
# it will insert a comment for the student.
Comment('An explanation')
Good: If the child test returns True then the student answer is good.
The child comment will be visible to the student even if it
returns False.
Good(Equal('5'))
Good(Contain('6'))
Good(~ Start('x'))
Grade: If the first expression is True:
Set a grade for the student+question+teacher.
The grade can be a positive or negative number.
The 'teacher' can be the name of a knowledge.
Grade always returns the value returned by the first expression.
The grades are summed per teacher when exporting all the grades.
# If the student answers:
# * '2' or 'two' then 'point' is set to 1.
# * 'two' then 'see_student' is set to 1
# * another integer then 'calculus' is set to -2
# * not in integer then no grades are changed.
Good(Grade(Grade(Equal('two'), "see_student", 1) | Int(2),
"point", 1)
),
Bad(Grade(~Int(1), "calculus", -2))
RemoveSpaces: Remove unecessary spaces/tabs.
White space at the end of line are removed, but not at the start.
So 'a + 5' become 'a+5'
But 'a 5' stays as 'a 5'
Replace: The first argument is a tuple of (old_string, new_string)
all the replacements are done on the student answer and
the test in parameter is then evaluated.
The replacements strings are NOT canonized by default.
# Student answers 'aba', 'ab1'... will pass this test.
Good(Replace( (('a', '1'), ('b', '2')),
Equal('121')))
# Beware single item python tuple, do not forget the coma:
Good(Replace( (('a', '1'), ),
Equal('121')))
# With UpperCase canoniser, canonization is a good idea
Good(UpperCase(Replace((('a', 'b'),), Equal('a'), canonize=True)))
SortLines: The lines of the student answer are sorted and child test value
is returned.
# The student answer 'b
be fine.
Good(SortLines(Equal('a
TestInvert: True if the children test returns False (or the reverse),
the syntax with '~' operator can be used.
# The answer is good if it is NOT 42.
Good(TestInvert(Equal('42')))
Good(~ Equal('42'))
UpperCase: The student answer is uppercased, and the child test value
is returned.
# The 'Equal' parameter is uppercased.
Good(UpperCase(Equal('aa'))) # True if answer is: 'aa', 'AA', 'Aa' or 'aA'
# The replacement is done before uppercasing, so if the answer
# contains 'A' it will not be translated into 'X'.
# So 'a', 'x' and 'X' are good answers, but not 'A'
Good(Replace((('a','x'),),
UpperCase(Equal('a'))
)
)
# To replace both 'a' and 'A' per 'X' the good order is:
# So 'a', 'A', 'x' and 'X' are good answers.
Good(UpperCase(Replace((('A','X'),),
Equal('a'))
)
)
# A more straightforward and intuitive coding is:
Good(UpperCase(Replace((('A','X'),),
Equal('X'))
)
)