Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
f2bb9a2d
Commit
f2bb9a2d
authored
Feb 26, 2013
by
Will Daly
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implemented unit tests for <customresponse>
parent
8b5473b6
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
300 additions
and
0 deletions
+300
-0
common/lib/capa/capa/tests/response_xml_factory.py
+184
-0
common/lib/capa/capa/tests/test_responsetypes.py
+116
-0
No files found.
common/lib/capa/capa/tests/response_xml_factory.py
0 → 100644
View file @
f2bb9a2d
from
lxml
import
etree
from
abc
import
ABCMeta
,
abstractmethod
class
ResponseXMLFactory
(
object
):
""" Abstract base class for capa response XML factories.
Subclasses override create_response_element and
create_input_element to produce XML of particular response types"""
__metaclass__
=
ABCMeta
@abstractmethod
def
create_response_element
(
self
,
**
kwargs
):
""" Subclasses override to return an etree element
representing the capa response XML
(e.g. <numericalresponse>).
The tree should NOT contain any input elements
(such as <textline />) as these will be added later."""
return
None
@abstractmethod
def
create_input_element
(
self
,
**
kwargs
):
""" Subclasses override this to return an etree element
representing the capa input XML (such as <textline />)"""
return
None
def
build_xml
(
self
,
**
kwargs
):
""" Construct an XML string for a capa response
based on **kwargs.
**kwargs is a dictionary that will be passed
to create_response_element() and create_input_element().
See the subclasses below for other keyword arguments
you can specify.
For all response types, **kwargs can contain:
'question_text': The text of the question to display,
wrapped in <p> tags.
'explanation_text': The detailed explanation that will
be shown if the user answers incorrectly.
'script': The embedded Python script (a string)
'num_inputs': The number of input elements
to create [DEFAULT: 1]
Returns a string representation of the XML tree.
"""
# Retrieve keyward arguments
question_text
=
kwargs
.
get
(
'question_text'
,
''
)
explanation_text
=
kwargs
.
get
(
'explanation_text'
)
script
=
kwargs
.
get
(
'script'
,
None
)
num_inputs
=
kwargs
.
get
(
'num_inputs'
,
1
)
# The root is <problem>
root
=
etree
.
Element
(
"problem"
)
# Add a script if there is one
if
script
:
script_element
=
etree
.
SubElement
(
root
,
"script"
)
script_element
.
set
(
"type"
,
"loncapa/python"
)
script_element
.
text
=
str
(
script
)
# The problem has a child <p> with question text
question
=
etree
.
SubElement
(
root
,
"p"
)
question
.
text
=
question_text
# Add the response
response_element
=
self
.
create_response_element
(
**
kwargs
)
root
.
append
(
response_element
)
# Add input elements
for
i
in
range
(
0
,
int
(
num_inputs
)):
input_element
=
self
.
create_input_element
(
**
kwargs
)
response_element
.
append
(
input_element
)
# The problem has an explanation of the solution
explanation
=
etree
.
SubElement
(
root
,
"solution"
)
explanation_div
=
etree
.
SubElement
(
explanation
,
"div"
)
explanation_div
.
set
(
"class"
,
"detailed-solution"
)
explanation_div
.
text
=
explanation_text
return
etree
.
tostring
(
root
)
@staticmethod
def
textline_input_xml
(
**
kwargs
):
""" Create a <textline/> XML element
Uses **kwargs:
'math_display': If True, then includes a MathJax display of user input
'size': An integer representing the width of the text line
"""
math_display
=
kwargs
.
get
(
'math_display'
,
False
)
size
=
kwargs
.
get
(
'size'
,
None
)
input_element
=
etree
.
Element
(
'textline'
)
if
math_display
:
input_element
.
set
(
'math'
,
'1'
)
if
size
:
input_element
.
set
(
'size'
,
str
(
size
))
return
input_element
class
NumericalResponseXMLFactory
(
ResponseXMLFactory
):
""" Factory for producing <numericalresponse> XML trees """
def
create_response_element
(
self
,
**
kwargs
):
""" Create a <numericalresponse> XML element.
Uses **kwarg keys:
'answer': The correct answer (e.g. "5")
'tolerance': The tolerance within which a response
is considered correct. Can be a decimal (e.g. "0.01")
or percentage (e.g. "2
%
")
"""
answer
=
kwargs
.
get
(
'answer'
,
None
)
tolerance
=
kwargs
.
get
(
'tolerance'
,
None
)
response_element
=
etree
.
Element
(
'numericalresponse'
)
if
answer
:
response_element
.
set
(
'answer'
,
str
(
answer
))
if
tolerance
:
responseparam_element
=
etree
.
SubElement
(
response_element
,
'responseparam'
)
responseparam_element
.
set
(
'type'
,
'tolerance'
)
responseparam_element
.
set
(
'default'
,
str
(
tolerance
))
return
response_element
def
create_input_element
(
self
,
**
kwargs
):
return
ResponseXMLFactory
.
textline_input_xml
(
**
kwargs
)
class
CustomResponseXMLFactory
(
ResponseXMLFactory
):
""" Factory for producing <customresponse> XML trees """
def
create_response_element
(
self
,
**
kwargs
):
""" Create a <customresponse> XML element.
Use **kwargs:
'cfn': the Python code to run. Can be inline code,
or the name of a function defined in earlier <script> tags.
Should have the form: cfn(expect, ans)
where expect is a value (see below)
and ans is a list of values.
'expect': The value passed as the first argument to the function cfn
'answer': Inline script that calculates the answer
"""
# Retrieve **kwargs
cfn
=
kwargs
.
get
(
'cfn'
,
None
)
expect
=
kwargs
.
get
(
'expect'
,
None
)
answer
=
kwargs
.
get
(
'answer'
,
None
)
# Create the response element
response_element
=
etree
.
Element
(
"customresponse"
)
if
cfn
:
response_element
.
set
(
'cfn'
,
str
(
cfn
))
if
expect
:
response_element
.
set
(
'expect'
,
str
(
expect
))
if
answer
:
answer_element
=
etree
.
SubElement
(
response_element
,
"answer"
)
answer_element
.
text
=
str
(
answer
)
return
response_element
def
create_input_element
(
self
,
**
kwargs
):
return
ResponseXMLFactory
.
textline_input_xml
(
**
kwargs
)
common/lib/capa/capa/tests/test_responsetypes.py
View file @
f2bb9a2d
...
...
@@ -493,3 +493,119 @@ class NumericalResponseTest(unittest.TestCase):
for
input_str
in
incorrect_answers
:
result
=
problem
.
grade_answers
({
'1_2_1'
:
input_str
})
.
get_correctness
(
'1_2_1'
)
self
.
assertEqual
(
result
,
'incorrect'
)
from
response_xml_factory
import
CustomResponseXMLFactory
class
CustomResponseTest
(
unittest
.
TestCase
):
def
setUp
(
self
):
self
.
xml_factory
=
CustomResponseXMLFactory
()
def
test_inline_code
(
self
):
# For inline code, we directly modify global context variables
# 'answers' is a list of answers provided to us
# 'correct' is a list we fill in with True/False
# 'expect' is given to us (if provided in the XML)
inline_script
=
"""correct[0] = 'correct' if (answers['1_2_1'] == expect) else 'incorrect'"""
xml
=
self
.
xml_factory
.
build_xml
(
answer
=
inline_script
,
expect
=
"42"
)
problem
=
lcp
.
LoncapaProblem
(
xml
,
'1'
,
system
=
test_system
)
# Correct answer
input_dict
=
{
'1_2_1'
:
'42'
}
result
=
problem
.
grade_answers
(
input_dict
)
.
get_correctness
(
'1_2_1'
)
self
.
assertEqual
(
result
,
'correct'
)
# Incorrect answer
input_dict
=
{
'1_2_1'
:
'0'
}
result
=
problem
.
grade_answers
(
input_dict
)
.
get_correctness
(
'1_2_1'
)
self
.
assertEqual
(
result
,
'incorrect'
)
def
test_inline_message
(
self
):
# Inline code can update the global messages list
# to pass messages to the CorrectMap for a particular input
inline_script
=
"""messages[0] = "Test Message" """
xml
=
self
.
xml_factory
.
build_xml
(
answer
=
inline_script
)
problem
=
lcp
.
LoncapaProblem
(
xml
,
'1'
,
system
=
test_system
)
input_dict
=
{
'1_2_1'
:
'0'
}
msg
=
problem
.
grade_answers
(
input_dict
)
.
get_msg
(
'1_2_1'
)
self
.
assertEqual
(
msg
,
"Test Message"
)
def
test_function_code
(
self
):
# For function code, we pass in three arguments:
#
# 'expect' is the expect attribute of the <customresponse>
#
# 'answer_given' is the answer the student gave (if there is just one input)
# or an ordered list of answers (if there are multiple inputs)
#
# 'student_answers' is a dictionary of answers by input ID
#
#
# The function should return a dict of the form
# { 'ok': BOOL, 'msg': STRING }
#
script
=
"""def check_func(expect, answer_given, student_answers):
return {'ok': answer_given == expect, 'msg': 'Message text'}"""
xml
=
self
.
xml_factory
.
build_xml
(
script
=
script
,
cfn
=
"check_func"
,
expect
=
"42"
)
problem
=
lcp
.
LoncapaProblem
(
xml
,
'1'
,
system
=
test_system
)
# Correct answer
input_dict
=
{
'1_2_1'
:
'42'
}
correct_map
=
problem
.
grade_answers
(
input_dict
)
correctness
=
correct_map
.
get_correctness
(
'1_2_1'
)
msg
=
correct_map
.
get_msg
(
'1_2_1'
)
self
.
assertEqual
(
correctness
,
'correct'
)
self
.
assertEqual
(
msg
,
"Message text
\n
"
)
# Incorrect answer
input_dict
=
{
'1_2_1'
:
'0'
}
correct_map
=
problem
.
grade_answers
(
input_dict
)
correctness
=
correct_map
.
get_correctness
(
'1_2_1'
)
msg
=
correct_map
.
get_msg
(
'1_2_1'
)
self
.
assertEqual
(
correctness
,
'incorrect'
)
self
.
assertEqual
(
msg
,
"Message text
\n
"
)
def
test_multiple_inputs
(
self
):
# When given multiple inputs, the 'answer_given' argument
# to the check_func() is a list of inputs
# The sample script below marks the problem as correct
# if and only if it receives answer_given=[1,2,3]
# (or string values ['1','2','3'])
script
=
"""def check_func(expect, answer_given, student_answers):
check1 = (int(answer_given[0]) == 1)
check2 = (int(answer_given[1]) == 2)
check3 = (int(answer_given[2]) == 3)
return {'ok': (check1 and check2 and check3), 'msg': 'Message text'}"""
xml
=
self
.
xml_factory
.
build_xml
(
script
=
script
,
cfn
=
"check_func"
,
num_inputs
=
3
)
problem
=
lcp
.
LoncapaProblem
(
xml
,
'1'
,
system
=
test_system
)
# Grade the inputs (one input incorrect)
input_dict
=
{
'1_2_1'
:
'-999'
,
'1_2_2'
:
'2'
,
'1_2_3'
:
'3'
}
correct_map
=
problem
.
grade_answers
(
input_dict
)
# Everything marked incorrect
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_1'
),
'incorrect'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_2'
),
'incorrect'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_3'
),
'incorrect'
)
# Grade the inputs (everything correct)
input_dict
=
{
'1_2_1'
:
'1'
,
'1_2_2'
:
'2'
,
'1_2_3'
:
'3'
}
correct_map
=
problem
.
grade_answers
(
input_dict
)
# Everything marked incorrect
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_1'
),
'correct'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_2'
),
'correct'
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_3'
),
'correct'
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment