Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-ora2
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-ora2
Commits
a33ad617
Commit
a33ad617
authored
Feb 28, 2014
by
Will Daly
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor test setup for XBlock rendering and handlers.
parent
e44b501c
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
225 additions
and
121 deletions
+225
-121
apps/openassessment/xblock/test/base.py
+135
-0
apps/openassessment/xblock/test/data/basic_scenario.xml
+42
-0
apps/openassessment/xblock/test/test_openassessment.py
+48
-121
No files found.
apps/openassessment/xblock/test/base.py
0 → 100644
View file @
a33ad617
"""
Base class for handler-level testing of the XBlock.
"""
import
os.path
import
json
from
functools
import
wraps
from
django.test
import
TestCase
from
workbench.runtime
import
WorkbenchRuntime
import
webob
def
scenario
(
scenario_path
,
user_id
=
None
):
"""
Method decorator to load a scenario for a test case.
Must be called on an `XBlockHandlerTestCase` subclass, or
else it will have no effect.
Args:
scenario_path (str): Path to the scenario XML file.
Kwargs:
user_id (str or None): User ID to log in as, or None.
Returns:
The decorated method
Example:
@scenario('data/test_scenario.xml')
def test_submit(self, xblock):
response = self.request(xblock, 'submit', 'Test submission')
self.assertTrue('Success' in response)
"""
def
_decorator
(
func
):
@wraps
(
func
)
def
_wrapped
(
*
args
,
**
kwargs
):
# Retrieve the object (self)
# if this is a function, not a method, then do nothing.
xblock
=
None
if
args
:
self
=
args
[
0
]
if
isinstance
(
self
,
XBlockHandlerTestCase
):
# Configure the runtime with our user id
self
.
set_user
(
user_id
)
# Load the scenario
xblock
=
self
.
load_scenario
(
scenario_path
)
# Pass the XBlock as the first argument to the decorated method (after `self`)
args
=
list
(
args
)
args
.
insert
(
1
,
xblock
)
return
func
(
*
args
,
**
kwargs
)
return
_wrapped
return
_decorator
class
XBlockHandlerTestCase
(
TestCase
):
"""
Load the XBlock in the workbench runtime to test its handler.
"""
def
setUp
(
self
):
"""
Create the runtime.
"""
self
.
runtime
=
WorkbenchRuntime
()
def
set_user
(
self
,
user_id
):
"""
Provide a user ID to the runtime.
Args:
user_id (str): a user ID.
Returns:
None
"""
self
.
runtime
.
user_id
=
user_id
def
load_scenario
(
self
,
xml_path
):
"""
Load an XML definition of an XBlock and return the XBlock instance.
Args:
xml (string): Path to an XML definition of the XBlock, relative
to the test module.
Returns:
XBlock
"""
base_dir
=
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))
with
open
(
os
.
path
.
join
(
base_dir
,
xml_path
))
as
xml_file
:
block_id
=
self
.
runtime
.
parse_xml_string
(
xml_file
.
read
(),
self
.
runtime
.
id_generator
)
return
self
.
runtime
.
get_block
(
block_id
)
def
request
(
self
,
xblock
,
handler_name
,
content
,
response_format
=
None
):
"""
Make a request to an XBlock handler.
Args:
xblock (XBlock): The XBlock instance that should handle the request.
handler_name (str): The name of the handler.
content (unicode): Content of the request.
Kwargs:
response_format (None or str): Expected format of the response string.
If `None`, return the raw response content; if 'json', parse the
response as JSON and return the result.
Raises:
NotImplementedError: Response format not supported.
Returns:
Content of the response (mixed).
"""
# Create a fake request
request
=
webob
.
Request
(
dict
())
request
.
body
=
content
# Send the request to the XBlock handler
response
=
self
.
runtime
.
handle
(
xblock
,
handler_name
,
request
)
# Parse the response (if a format is specified)
if
response_format
is
None
:
return
response
.
body
elif
response_format
==
'json'
:
return
json
.
loads
(
response
.
body
)
else
:
raise
NotImplementedError
(
"Response format '{format}' not supported"
.
format
(
response_format
))
apps/openassessment/xblock/test/data/basic_scenario.xml
0 → 100644
View file @
a33ad617
<openassessment
start=
"2014-12-19T23:00:00"
due=
"2014-12-21T23:00:00"
>
<prompt>
Given the state of the world today, what do you think should be done to
combat poverty? Please answer in a short essay of 200-300 words.
</prompt>
<rubric>
Read for conciseness, clarity of thought, and form.
<criterion
name=
"concise"
>
How concise is it?
<option
val=
"0"
>
Neal Stephenson (late)
</option>
<option
val=
"1"
>
HP Lovecraft
</option>
<option
val=
"3"
>
Robert Heinlein
</option>
<option
val=
"4"
>
Neal Stephenson (early)
</option>
<option
val=
"5"
>
Earnest Hemingway
</option>
</criterion>
<criterion
name=
"clearheaded"
>
How clear is the thinking?
<option
val=
"0"
>
Yogi Berra
</option>
<option
val=
"1"
>
Hunter S. Thompson
</option>
<option
val=
"2"
>
Robert Heinlein
</option>
<option
val=
"3"
>
Isaac Asimov
</option>
<option
val=
"10"
>
Spock
</option>
</criterion>
<criterion
name=
"form"
>
Lastly, how is it's form? Punctuation, grammar, and spelling all count.
<option
val=
"0"
>
lolcats
</option>
<option
val=
"1"
>
Facebook
</option>
<option
val=
"2"
>
Reddit
</option>
<option
val=
"3"
>
metafilter
</option>
<option
val=
"4"
>
Usenet, 1996
</option>
<option
val=
"5"
>
The Elements of Style
</option>
</criterion>
</rubric>
<assessments>
<peer-assessment
name=
"peer-assessment"
start=
"2014-12-20T19:00"
due=
"2014-12-21T22:22"
must_grade=
"5"
must_be_graded_by=
"3"
/>
<self-assessment
name=
"self-assessment"
/>
</assessments>
</openassessment>
apps/openassessment/xblock/test/test_openassessment.py
View file @
a33ad617
...
...
@@ -4,164 +4,91 @@ Tests the Open Assessment XBlock functionality.
import
json
import
datetime
from
django.test
import
TestCase
from
mock
import
patch
from
workbench.runtime
import
WorkbenchRuntime
import
webob
from
openassessment.xblock.submission_mixin
import
SubmissionMixin
from
submissions
import
api
as
sub_api
from
submissions.api
import
SubmissionRequestError
,
SubmissionInternalError
RUBRIC_CONFIG
=
"""
<openassessment start="2014-12-19T23:00:00" due="2014-12-21T23:00:00">
<prompt>
Given the state of the world today, what do you think should be done to
combat poverty? Please answer in a short essay of 200-300 words.
</prompt>
<rubric>
Read for conciseness, clarity of thought, and form.
<criterion name="concise">
How concise is it?
<option val="0">Neal Stephenson (late)</option>
<option val="1">HP Lovecraft</option>
<option val="3">Robert Heinlein</option>
<option val="4">Neal Stephenson (early)</option>
<option val="5">Earnest Hemingway</option>
</criterion>
<criterion name="clearheaded">
How clear is the thinking?
<option val="0">Yogi Berra</option>
<option val="1">Hunter S. Thompson</option>
<option val="2">Robert Heinlein</option>
<option val="3">Isaac Asimov</option>
<option val="10">Spock</option>
</criterion>
<criterion name="form">
Lastly, how is it's form? Punctuation, grammar, and spelling all count.
<option val="0">lolcats</option>
<option val="1">Facebook</option>
<option val="2">Reddit</option>
<option val="3">metafilter</option>
<option val="4">Usenet, 1996</option>
<option val="5">The Elements of Style</option>
</criterion>
</rubric>
<assessments>
<peer-assessment name="peer-assessment"
start="2014-12-20T19:00"
due="2014-12-21T22:22"
must_grade="5"
must_be_graded_by="3" />
<self-assessment name="self-assessment"/>
</assessments>
</openassessment>
"""
from
.base
import
XBlockHandlerTestCase
,
scenario
class
TestOpenAssessment
(
TestCase
):
runtime
=
None
assessment
=
None
class
TestOpenAssessment
(
XBlockHandlerTestCase
):
def
setUp
(
self
):
self
.
runtime
=
WorkbenchRuntime
()
self
.
runtime
.
user_id
=
"Bob"
assessment_id
=
self
.
runtime
.
parse_xml_string
(
RUBRIC_CONFIG
,
self
.
runtime
.
id_generator
)
self
.
assessment
=
self
.
runtime
.
get_block
(
assessment_id
)
self
.
default_json_submission
=
json
.
dumps
({
"submission"
:
"This is my answer to this test question!"
})
SUBMISSION
=
json
.
dumps
({
"submission"
:
"This is my answer to this test question!"
})
def
make_request
(
self
,
body
):
"""Mock request method."""
request
=
webob
.
Request
({})
request
.
body
=
body
return
request
def
test_submit_submission
(
self
):
@scenario
(
'data/basic_scenario.xml'
,
user_id
=
'Bob'
)
def
test_submit_submission
(
self
,
xblock
):
"""XBlock accepts response, returns true on success"""
# This one should pass because we haven't submitted before
resp
=
self
.
runtime
.
handle
(
self
.
assessment
,
'submit'
,
self
.
make_request
(
self
.
default_json_submission
)
)
result
=
json
.
loads
(
resp
.
body
)
self
.
assertTrue
(
result
[
0
])
def
test_submission_multisubmit_failure
(
self
):
resp
=
self
.
request
(
xblock
,
'submit'
,
self
.
SUBMISSION
,
response_format
=
'json'
)
self
.
assertTrue
(
resp
[
0
])
@scenario
(
'data/basic_scenario.xml'
,
user_id
=
'Bob'
)
def
test_submission_multisubmit_failure
(
self
,
xblock
):
"""XBlock returns true on first, false on second submission"""
# We don't care about return value of first one
resp
=
self
.
runtime
.
handle
(
self
.
assessment
,
'submit'
,
self
.
make_request
(
self
.
default_json_submission
)
)
self
.
request
(
xblock
,
'submit'
,
self
.
SUBMISSION
,
response_format
=
'json'
)
# This one should fail becaus we're not allowed to submit multiple times
resp
=
self
.
runtime
.
handle
(
self
.
assessment
,
'submit'
,
self
.
make_request
(
self
.
default_json_submission
)
)
result
=
json
.
loads
(
resp
.
body
)
self
.
assertFalse
(
result
[
0
])
self
.
assertEqual
(
result
[
1
],
"ENOMULTI"
)
self
.
assertEqual
(
result
[
2
],
self
.
assessment
.
submit_errors
[
"ENOMULTI"
])
resp
=
self
.
request
(
xblock
,
'submit'
,
self
.
SUBMISSION
,
response_format
=
'json'
)
self
.
assertFalse
(
resp
[
0
])
self
.
assertEqual
(
resp
[
1
],
"ENOMULTI"
)
self
.
assertEqual
(
resp
[
2
],
xblock
.
submit_errors
[
"ENOMULTI"
])
@scenario
(
'data/basic_scenario.xml'
)
@patch.object
(
sub_api
,
'create_submission'
)
def
test_submission_general_failure
(
self
,
mock_submit
):
def
test_submission_general_failure
(
self
,
xblock
,
mock_submit
):
"""Internal errors return some code for submission failure."""
mock_submit
.
side_effect
=
SubmissionInternalError
(
"Cat on fire."
)
resp
=
self
.
runtime
.
handle
(
self
.
assessment
,
'submit'
,
self
.
make_request
(
self
.
default_json_submission
)
)
result
=
json
.
loads
(
resp
.
body
)
self
.
assertFalse
(
result
[
0
])
self
.
assertEqual
(
result
[
1
],
"EUNKNOWN"
)
self
.
assertEqual
(
result
[
2
],
SubmissionMixin
()
.
submit_errors
[
"EUNKNOWN"
])
resp
=
self
.
request
(
xblock
,
'submit'
,
self
.
SUBMISSION
,
response_format
=
'json'
)
self
.
assertFalse
(
resp
[
0
])
self
.
assertEqual
(
resp
[
1
],
"EUNKNOWN"
)
self
.
assertEqual
(
resp
[
2
],
SubmissionMixin
()
.
submit_errors
[
"EUNKNOWN"
])
@scenario
(
'data/basic_scenario.xml'
)
@patch.object
(
sub_api
,
'create_submission'
)
def
test_submission_API_failure
(
self
,
mock_submit
):
def
test_submission_API_failure
(
self
,
xblock
,
mock_submit
):
"""API usage errors return code and meaningful message."""
mock_submit
.
side_effect
=
SubmissionRequestError
(
"Cat on fire."
)
resp
=
self
.
runtime
.
handle
(
self
.
assessment
,
'submit'
,
self
.
make_request
(
self
.
default_json_submission
)
)
result
=
json
.
loads
(
resp
.
body
)
self
.
assertFalse
(
result
[
0
])
self
.
assertEqual
(
result
[
1
],
"EBADFORM"
)
self
.
assertEqual
(
result
[
2
],
"Cat on fire."
)
def
test_load_student_view
(
self
):
resp
=
self
.
request
(
xblock
,
'submit'
,
self
.
SUBMISSION
,
response_format
=
'json'
)
self
.
assertFalse
(
resp
[
0
])
self
.
assertEqual
(
resp
[
1
],
"EBADFORM"
)
self
.
assertEqual
(
resp
[
2
],
"Cat on fire."
)
@scenario
(
'data/basic_scenario.xml'
)
def
test_load_student_view
(
self
,
xblock
):
"""OA XBlock returns some HTML to the user.
View basic test for verifying we're returned some HTML about the
Open Assessment XBlock. We don't want to match too heavily against the
contents.
"""
xblock_fragment
=
self
.
runtime
.
render
(
self
.
assessment
,
"student_view"
)
xblock_fragment
=
self
.
runtime
.
render
(
xblock
,
"student_view"
)
self
.
assertTrue
(
xblock_fragment
.
body_html
()
.
find
(
"Openassessmentblock"
))
# Validate Submission Rendering.
submission_response
=
self
.
assessment
.
render_submission
({})
submission_response
=
xblock
.
render_submission
({})
self
.
assertIsNotNone
(
submission_response
)
self
.
assertTrue
(
submission_response
.
body
.
find
(
"openassessment__response"
))
# Validate Peer Rendering.
peer_response
=
self
.
assessment
.
render_peer_assessment
({})
peer_response
=
xblock
.
render_peer_assessment
({})
self
.
assertIsNotNone
(
peer_response
)
self
.
assertTrue
(
peer_response
.
body
.
find
(
"openassessment__peer-assessment"
))
# Validate Self Rendering.
self_response
=
self
.
assessment
.
render_self_assessment
({})
self_response
=
xblock
.
render_self_assessment
({})
self
.
assertIsNotNone
(
self_response
)
self
.
assertTrue
(
self_response
.
body
.
find
(
"openassessment__peer-assessment"
))
# Validate Grading.
grade_response
=
self
.
assessment
.
render_grade
({})
grade_response
=
xblock
.
render_grade
({})
self
.
assertIsNotNone
(
grade_response
)
self
.
assertTrue
(
grade_response
.
body
.
find
(
"openassessment__grade"
))
def
test_start_end_date_checks
(
self
):
@scenario
(
'data/basic_scenario.xml'
)
def
test_start_end_date_checks
(
self
,
xblock
):
"""
Check if the start and end date checks work appropriately.
"""
...
...
@@ -169,20 +96,20 @@ class TestOpenAssessment(TestCase):
past
=
now
-
datetime
.
timedelta
(
minutes
=
10
)
future
=
now
+
datetime
.
timedelta
(
minutes
=
10
)
way_future
=
now
+
datetime
.
timedelta
(
minutes
=
20
)
self
.
assessment
.
start_datetime
=
past
.
isoformat
()
self
.
assessment
.
due_datetime
=
past
.
isoformat
()
problem_open
,
reason
=
self
.
assessment
.
is_open
()
xblock
.
start_datetime
=
past
.
isoformat
()
xblock
.
due_datetime
=
past
.
isoformat
()
problem_open
,
reason
=
xblock
.
is_open
()
self
.
assertFalse
(
problem_open
)
self
.
assertEqual
(
"due"
,
reason
)
self
.
assessment
.
start_datetime
=
past
.
isoformat
()
self
.
assessment
.
due_datetime
=
future
.
isoformat
()
problem_open
,
reason
=
self
.
assessment
.
is_open
()
xblock
.
start_datetime
=
past
.
isoformat
()
xblock
.
due_datetime
=
future
.
isoformat
()
problem_open
,
reason
=
xblock
.
is_open
()
self
.
assertTrue
(
problem_open
)
self
.
assertEqual
(
None
,
reason
)
self
.
assessment
.
start_datetime
=
future
.
isoformat
()
self
.
assessment
.
due_datetime
=
way_future
.
isoformat
()
problem_open
,
reason
=
self
.
assessment
.
is_open
()
xblock
.
start_datetime
=
future
.
isoformat
()
xblock
.
due_datetime
=
way_future
.
isoformat
()
problem_open
,
reason
=
xblock
.
is_open
()
self
.
assertFalse
(
problem_open
)
self
.
assertEqual
(
"start"
,
reason
)
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