Move tests around:
- move progress tests into test_progress.py - move grader tests into test_graders.py - move the capa tests into capa/tests/
This commit is contained in:
@@ -1,31 +1,22 @@
|
||||
#
|
||||
# unittests for xmodule (and capa)
|
||||
#
|
||||
# Note: run this using a like like this:
|
||||
#
|
||||
# django-admin.py test --settings=lms.envs.test_ike --pythonpath=. common/lib/xmodule
|
||||
"""
|
||||
unittests for xmodule
|
||||
|
||||
Run like this:
|
||||
|
||||
rake test_common/lib/xmodule
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import fs
|
||||
import fs.osfs
|
||||
import json
|
||||
|
||||
import json
|
||||
import numpy
|
||||
|
||||
import xmodule
|
||||
import capa.calc as calc
|
||||
import capa.capa_problem as lcp
|
||||
from capa.correctmap import CorrectMap
|
||||
from capa.util import convert_files_to_filenames
|
||||
from capa.xqueue_interface import dateformat
|
||||
from datetime import datetime
|
||||
from xmodule import graders, x_module
|
||||
import xmodule
|
||||
from xmodule.x_module import ModuleSystem
|
||||
from xmodule.graders import Score, aggregate_scores
|
||||
from xmodule.progress import Progress
|
||||
from nose.plugins.skip import SkipTest
|
||||
from mock import Mock
|
||||
|
||||
i4xs = ModuleSystem(
|
||||
@@ -35,7 +26,7 @@ i4xs = ModuleSystem(
|
||||
render_template=Mock(),
|
||||
replace_urls=Mock(),
|
||||
user=Mock(),
|
||||
filestore=fs.osfs.OSFS(os.path.dirname(os.path.realpath(__file__))+"/test_files"),
|
||||
filestore=Mock(),
|
||||
debug=True,
|
||||
xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10},
|
||||
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
|
||||
@@ -94,719 +85,3 @@ class ModelsTest(unittest.TestCase):
|
||||
exception_happened = True
|
||||
self.assertTrue(exception_happened)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# tests of capa_problem inputtypes
|
||||
|
||||
|
||||
class MultiChoiceTest(unittest.TestCase):
|
||||
def test_MC_grade(self):
|
||||
multichoice_file = os.path.dirname(__file__) + "/test_files/multichoice.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(multichoice_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': 'choice_foil3'}
|
||||
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
||||
false_answers = {'1_2_1': 'choice_foil2'}
|
||||
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
|
||||
|
||||
def test_MC_bare_grades(self):
|
||||
multichoice_file = os.path.dirname(__file__) + "/test_files/multi_bare.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(multichoice_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': 'choice_2'}
|
||||
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
||||
false_answers = {'1_2_1': 'choice_1'}
|
||||
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
|
||||
|
||||
def test_TF_grade(self):
|
||||
truefalse_file = os.path.dirname(__file__) + "/test_files/truefalse.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(truefalse_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': ['choice_foil2', 'choice_foil1']}
|
||||
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
||||
false_answers = {'1_2_1': ['choice_foil1']}
|
||||
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
|
||||
false_answers = {'1_2_1': ['choice_foil1', 'choice_foil3']}
|
||||
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
|
||||
false_answers = {'1_2_1': ['choice_foil3']}
|
||||
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
|
||||
false_answers = {'1_2_1': ['choice_foil1', 'choice_foil2', 'choice_foil3']}
|
||||
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
|
||||
|
||||
|
||||
class ImageResponseTest(unittest.TestCase):
|
||||
def test_ir_grade(self):
|
||||
imageresponse_file = os.path.dirname(__file__) + "/test_files/imageresponse.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(imageresponse_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': '(490,11)-(556,98)',
|
||||
'1_2_2': '(242,202)-(296,276)'}
|
||||
test_answers = {'1_2_1': '[500,20]',
|
||||
'1_2_2': '[250,300]',
|
||||
}
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_2'), 'incorrect')
|
||||
|
||||
|
||||
class SymbolicResponseTest(unittest.TestCase):
|
||||
def test_sr_grade(self):
|
||||
raise SkipTest() # This test fails due to dependencies on a local copy of snuggletex-webapp. Until we have figured that out, we'll just skip this test
|
||||
symbolicresponse_file = os.path.dirname(__file__) + "/test_files/symbolicresponse.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(symbolicresponse_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': 'cos(theta)*[[1,0],[0,1]] + i*sin(theta)*[[0,1],[1,0]]',
|
||||
'1_2_1_dynamath': '''
|
||||
<math xmlns="http://www.w3.org/1998/Math/MathML">
|
||||
<mstyle displaystyle="true">
|
||||
<mrow>
|
||||
<mi>cos</mi>
|
||||
<mrow>
|
||||
<mo>(</mo>
|
||||
<mi>θ</mi>
|
||||
<mo>)</mo>
|
||||
</mrow>
|
||||
</mrow>
|
||||
<mo>⋅</mo>
|
||||
<mrow>
|
||||
<mo>[</mo>
|
||||
<mtable>
|
||||
<mtr>
|
||||
<mtd>
|
||||
<mn>1</mn>
|
||||
</mtd>
|
||||
<mtd>
|
||||
<mn>0</mn>
|
||||
</mtd>
|
||||
</mtr>
|
||||
<mtr>
|
||||
<mtd>
|
||||
<mn>0</mn>
|
||||
</mtd>
|
||||
<mtd>
|
||||
<mn>1</mn>
|
||||
</mtd>
|
||||
</mtr>
|
||||
</mtable>
|
||||
<mo>]</mo>
|
||||
</mrow>
|
||||
<mo>+</mo>
|
||||
<mi>i</mi>
|
||||
<mo>⋅</mo>
|
||||
<mrow>
|
||||
<mi>sin</mi>
|
||||
<mrow>
|
||||
<mo>(</mo>
|
||||
<mi>θ</mi>
|
||||
<mo>)</mo>
|
||||
</mrow>
|
||||
</mrow>
|
||||
<mo>⋅</mo>
|
||||
<mrow>
|
||||
<mo>[</mo>
|
||||
<mtable>
|
||||
<mtr>
|
||||
<mtd>
|
||||
<mn>0</mn>
|
||||
</mtd>
|
||||
<mtd>
|
||||
<mn>1</mn>
|
||||
</mtd>
|
||||
</mtr>
|
||||
<mtr>
|
||||
<mtd>
|
||||
<mn>1</mn>
|
||||
</mtd>
|
||||
<mtd>
|
||||
<mn>0</mn>
|
||||
</mtd>
|
||||
</mtr>
|
||||
</mtable>
|
||||
<mo>]</mo>
|
||||
</mrow>
|
||||
</mstyle>
|
||||
</math>
|
||||
''',
|
||||
}
|
||||
wrong_answers = {'1_2_1': '2',
|
||||
'1_2_1_dynamath': '''
|
||||
<math xmlns="http://www.w3.org/1998/Math/MathML">
|
||||
<mstyle displaystyle="true">
|
||||
<mn>2</mn>
|
||||
</mstyle>
|
||||
</math>''',
|
||||
}
|
||||
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
||||
self.assertEquals(test_lcp.grade_answers(wrong_answers).get_correctness('1_2_1'), 'incorrect')
|
||||
|
||||
|
||||
class OptionResponseTest(unittest.TestCase):
|
||||
'''
|
||||
Run this with
|
||||
|
||||
python manage.py test courseware.OptionResponseTest
|
||||
'''
|
||||
def test_or_grade(self):
|
||||
optionresponse_file = os.path.dirname(__file__) + "/test_files/optionresponse.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(optionresponse_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': 'True',
|
||||
'1_2_2': 'False'}
|
||||
test_answers = {'1_2_1': 'True',
|
||||
'1_2_2': 'True',
|
||||
}
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_2'), 'incorrect')
|
||||
|
||||
|
||||
class FormulaResponseWithHintTest(unittest.TestCase):
|
||||
'''
|
||||
Test Formula response problem with a hint
|
||||
This problem also uses calc.
|
||||
'''
|
||||
def test_or_grade(self):
|
||||
problem_file = os.path.dirname(__file__) + "/test_files/formularesponse_with_hint.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': '2.5*x-5.0'}
|
||||
test_answers = {'1_2_1': '0.4*x-5.0'}
|
||||
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
||||
cmap = test_lcp.grade_answers(test_answers)
|
||||
self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect')
|
||||
self.assertTrue('You have inverted' in cmap.get_hint('1_2_1'))
|
||||
|
||||
|
||||
class StringResponseWithHintTest(unittest.TestCase):
|
||||
'''
|
||||
Test String response problem with a hint
|
||||
'''
|
||||
def test_or_grade(self):
|
||||
problem_file = os.path.dirname(__file__) + "/test_files/stringresponse_with_hint.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': 'Michigan'}
|
||||
test_answers = {'1_2_1': 'Minnesota'}
|
||||
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
||||
cmap = test_lcp.grade_answers(test_answers)
|
||||
self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect')
|
||||
self.assertTrue('St. Paul' in cmap.get_hint('1_2_1'))
|
||||
|
||||
|
||||
class CodeResponseTest(unittest.TestCase):
|
||||
'''
|
||||
Test CodeResponse
|
||||
TODO: Add tests for external grader messages
|
||||
'''
|
||||
@staticmethod
|
||||
def make_queuestate(key, time):
|
||||
timestr = datetime.strftime(time, dateformat)
|
||||
return {'key': key, 'time': timestr}
|
||||
|
||||
def test_is_queued(self):
|
||||
'''
|
||||
Simple test of whether LoncapaProblem knows when it's been queued
|
||||
'''
|
||||
problem_file = os.path.join(os.path.dirname(__file__), "test_files/coderesponse.xml")
|
||||
with open(problem_file) as input_file:
|
||||
test_lcp = lcp.LoncapaProblem(input_file.read(), '1', system=i4xs)
|
||||
|
||||
answer_ids = sorted(test_lcp.get_question_answers())
|
||||
|
||||
# CodeResponse requires internal CorrectMap state. Build it now in the unqueued state
|
||||
cmap = CorrectMap()
|
||||
for answer_id in answer_ids:
|
||||
cmap.update(CorrectMap(answer_id=answer_id, queuestate=None))
|
||||
test_lcp.correct_map.update(cmap)
|
||||
|
||||
self.assertEquals(test_lcp.is_queued(), False)
|
||||
|
||||
# Now we queue the LCP
|
||||
cmap = CorrectMap()
|
||||
for i, answer_id in enumerate(answer_ids):
|
||||
queuestate = CodeResponseTest.make_queuestate(i, datetime.now())
|
||||
cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
|
||||
test_lcp.correct_map.update(cmap)
|
||||
|
||||
self.assertEquals(test_lcp.is_queued(), True)
|
||||
|
||||
|
||||
def test_update_score(self):
|
||||
'''
|
||||
Test whether LoncapaProblem.update_score can deliver queued result to the right subproblem
|
||||
'''
|
||||
problem_file = os.path.join(os.path.dirname(__file__), "test_files/coderesponse.xml")
|
||||
with open(problem_file) as input_file:
|
||||
test_lcp = lcp.LoncapaProblem(input_file.read(), '1', system=i4xs)
|
||||
|
||||
answer_ids = sorted(test_lcp.get_question_answers())
|
||||
|
||||
# CodeResponse requires internal CorrectMap state. Build it now in the queued state
|
||||
old_cmap = CorrectMap()
|
||||
for i, answer_id in enumerate(answer_ids):
|
||||
queuekey = 1000 + i
|
||||
queuestate = CodeResponseTest.make_queuestate(1000+i, datetime.now())
|
||||
old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
|
||||
|
||||
# Message format common to external graders
|
||||
grader_msg = '<span>MESSAGE</span>' # Must be valid XML
|
||||
correct_score_msg = json.dumps({'correct':True, 'score':1, 'msg': grader_msg})
|
||||
incorrect_score_msg = json.dumps({'correct':False, 'score':0, 'msg': grader_msg})
|
||||
|
||||
xserver_msgs = {'correct': correct_score_msg,
|
||||
'incorrect': incorrect_score_msg,}
|
||||
|
||||
# Incorrect queuekey, state should not be updated
|
||||
for correctness in ['correct', 'incorrect']:
|
||||
test_lcp.correct_map = CorrectMap()
|
||||
test_lcp.correct_map.update(old_cmap) # Deep copy
|
||||
|
||||
test_lcp.update_score(xserver_msgs[correctness], queuekey=0)
|
||||
self.assertEquals(test_lcp.correct_map.get_dict(), old_cmap.get_dict()) # Deep comparison
|
||||
|
||||
for answer_id in answer_ids:
|
||||
self.assertTrue(test_lcp.correct_map.is_queued(answer_id)) # Should be still queued, since message undelivered
|
||||
|
||||
# Correct queuekey, state should be updated
|
||||
for correctness in ['correct', 'incorrect']:
|
||||
for i, answer_id in enumerate(answer_ids):
|
||||
test_lcp.correct_map = CorrectMap()
|
||||
test_lcp.correct_map.update(old_cmap)
|
||||
|
||||
new_cmap = CorrectMap()
|
||||
new_cmap.update(old_cmap)
|
||||
npoints = 1 if correctness=='correct' else 0
|
||||
new_cmap.set(answer_id=answer_id, npoints=npoints, correctness=correctness, msg=grader_msg, queuestate=None)
|
||||
|
||||
test_lcp.update_score(xserver_msgs[correctness], queuekey=1000 + i)
|
||||
self.assertEquals(test_lcp.correct_map.get_dict(), new_cmap.get_dict())
|
||||
|
||||
for j, test_id in enumerate(answer_ids):
|
||||
if j == i:
|
||||
self.assertFalse(test_lcp.correct_map.is_queued(test_id)) # Should be dequeued, message delivered
|
||||
else:
|
||||
self.assertTrue(test_lcp.correct_map.is_queued(test_id)) # Should be queued, message undelivered
|
||||
|
||||
|
||||
def test_recentmost_queuetime(self):
|
||||
'''
|
||||
Test whether the LoncapaProblem knows about the time of queue requests
|
||||
'''
|
||||
problem_file = os.path.join(os.path.dirname(__file__), "test_files/coderesponse.xml")
|
||||
with open(problem_file) as input_file:
|
||||
test_lcp = lcp.LoncapaProblem(input_file.read(), '1', system=i4xs)
|
||||
|
||||
answer_ids = sorted(test_lcp.get_question_answers())
|
||||
|
||||
# CodeResponse requires internal CorrectMap state. Build it now in the unqueued state
|
||||
cmap = CorrectMap()
|
||||
for answer_id in answer_ids:
|
||||
cmap.update(CorrectMap(answer_id=answer_id, queuestate=None))
|
||||
test_lcp.correct_map.update(cmap)
|
||||
|
||||
self.assertEquals(test_lcp.get_recentmost_queuetime(), None)
|
||||
|
||||
# CodeResponse requires internal CorrectMap state. Build it now in the queued state
|
||||
cmap = CorrectMap()
|
||||
for i, answer_id in enumerate(answer_ids):
|
||||
queuekey = 1000 + i
|
||||
latest_timestamp = datetime.now()
|
||||
queuestate = CodeResponseTest.make_queuestate(1000+i, latest_timestamp)
|
||||
cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate))
|
||||
test_lcp.correct_map.update(cmap)
|
||||
|
||||
# Queue state only tracks up to second
|
||||
latest_timestamp = datetime.strptime(datetime.strftime(latest_timestamp, dateformat), dateformat)
|
||||
|
||||
self.assertEquals(test_lcp.get_recentmost_queuetime(), latest_timestamp)
|
||||
|
||||
def test_convert_files_to_filenames(self):
|
||||
'''
|
||||
Test whether file objects are converted to filenames without altering other structures
|
||||
'''
|
||||
problem_file = os.path.join(os.path.dirname(__file__), "test_files/coderesponse.xml")
|
||||
with open(problem_file) as fp:
|
||||
answers_with_file = {'1_2_1': 'String-based answer',
|
||||
'1_3_1': ['answer1', 'answer2', 'answer3'],
|
||||
'1_4_1': [fp, fp]}
|
||||
answers_converted = convert_files_to_filenames(answers_with_file)
|
||||
self.assertEquals(answers_converted['1_2_1'], 'String-based answer')
|
||||
self.assertEquals(answers_converted['1_3_1'], ['answer1', 'answer2', 'answer3'])
|
||||
self.assertEquals(answers_converted['1_4_1'], [fp.name, fp.name])
|
||||
|
||||
|
||||
class ChoiceResponseTest(unittest.TestCase):
|
||||
|
||||
def test_cr_rb_grade(self):
|
||||
problem_file = os.path.dirname(__file__) + "/test_files/choiceresponse_radio.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': 'choice_2',
|
||||
'1_3_1': ['choice_2', 'choice_3']}
|
||||
test_answers = {'1_2_1': 'choice_2',
|
||||
'1_3_1': 'choice_2',
|
||||
}
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect')
|
||||
|
||||
def test_cr_cb_grade(self):
|
||||
problem_file = os.path.dirname(__file__) + "/test_files/choiceresponse_checkbox.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': 'choice_2',
|
||||
'1_3_1': ['choice_2', 'choice_3'],
|
||||
'1_4_1': ['choice_2', 'choice_3']}
|
||||
test_answers = {'1_2_1': 'choice_2',
|
||||
'1_3_1': 'choice_2',
|
||||
'1_4_1': ['choice_2', 'choice_3'],
|
||||
}
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect')
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_4_1'), 'correct')
|
||||
|
||||
class JavascriptResponseTest(unittest.TestCase):
|
||||
|
||||
def test_jr_grade(self):
|
||||
problem_file = os.path.dirname(__file__) + "/test_files/javascriptresponse.xml"
|
||||
coffee_file_path = os.path.dirname(__file__) + "/test_files/js/*.coffee"
|
||||
os.system("coffee -c %s" % (coffee_file_path))
|
||||
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
|
||||
correct_answers = {'1_2_1': json.dumps({0: 4})}
|
||||
incorrect_answers = {'1_2_1': json.dumps({0: 5})}
|
||||
|
||||
self.assertEquals(test_lcp.grade_answers(incorrect_answers).get_correctness('1_2_1'), 'incorrect')
|
||||
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Grading tests
|
||||
|
||||
|
||||
class GradesheetTest(unittest.TestCase):
|
||||
|
||||
def test_weighted_grading(self):
|
||||
scores = []
|
||||
Score.__sub__ = lambda me, other: (me.earned - other.earned) + (me.possible - other.possible)
|
||||
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertEqual(all, Score(earned=0, possible=0, graded=False, section="summary"))
|
||||
self.assertEqual(graded, Score(earned=0, possible=0, graded=True, section="summary"))
|
||||
|
||||
scores.append(Score(earned=0, possible=5, graded=False, section="summary"))
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertEqual(all, Score(earned=0, possible=5, graded=False, section="summary"))
|
||||
self.assertEqual(graded, Score(earned=0, possible=0, graded=True, section="summary"))
|
||||
|
||||
scores.append(Score(earned=3, possible=5, graded=True, section="summary"))
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertAlmostEqual(all, Score(earned=3, possible=10, graded=False, section="summary"))
|
||||
self.assertAlmostEqual(graded, Score(earned=3, possible=5, graded=True, section="summary"))
|
||||
|
||||
scores.append(Score(earned=2, possible=5, graded=True, section="summary"))
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertAlmostEqual(all, Score(earned=5, possible=15, graded=False, section="summary"))
|
||||
self.assertAlmostEqual(graded, Score(earned=5, possible=10, graded=True, section="summary"))
|
||||
|
||||
|
||||
class GraderTest(unittest.TestCase):
|
||||
|
||||
empty_gradesheet = {
|
||||
}
|
||||
|
||||
incomplete_gradesheet = {
|
||||
'Homework': [],
|
||||
'Lab': [],
|
||||
'Midterm': [],
|
||||
}
|
||||
|
||||
test_gradesheet = {
|
||||
'Homework': [Score(earned=2, possible=20.0, graded=True, section='hw1'),
|
||||
Score(earned=16, possible=16.0, graded=True, section='hw2')],
|
||||
#The dropped scores should be from the assignments that don't exist yet
|
||||
|
||||
'Lab': [Score(earned=1, possible=2.0, graded=True, section='lab1'), # Dropped
|
||||
Score(earned=1, possible=1.0, graded=True, section='lab2'),
|
||||
Score(earned=1, possible=1.0, graded=True, section='lab3'),
|
||||
Score(earned=5, possible=25.0, graded=True, section='lab4'), # Dropped
|
||||
Score(earned=3, possible=4.0, graded=True, section='lab5'), # Dropped
|
||||
Score(earned=6, possible=7.0, graded=True, section='lab6'),
|
||||
Score(earned=5, possible=6.0, graded=True, section='lab7')],
|
||||
|
||||
'Midterm': [Score(earned=50.5, possible=100, graded=True, section="Midterm Exam"), ],
|
||||
}
|
||||
|
||||
def test_SingleSectionGrader(self):
|
||||
midtermGrader = graders.SingleSectionGrader("Midterm", "Midterm Exam")
|
||||
lab4Grader = graders.SingleSectionGrader("Lab", "lab4")
|
||||
badLabGrader = graders.SingleSectionGrader("Lab", "lab42")
|
||||
|
||||
for graded in [midtermGrader.grade(self.empty_gradesheet),
|
||||
midtermGrader.grade(self.incomplete_gradesheet),
|
||||
badLabGrader.grade(self.test_gradesheet)]:
|
||||
self.assertEqual(len(graded['section_breakdown']), 1)
|
||||
self.assertEqual(graded['percent'], 0.0)
|
||||
|
||||
graded = midtermGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.505)
|
||||
self.assertEqual(len(graded['section_breakdown']), 1)
|
||||
|
||||
graded = lab4Grader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.2)
|
||||
self.assertEqual(len(graded['section_breakdown']), 1)
|
||||
|
||||
def test_AssignmentFormatGrader(self):
|
||||
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
|
||||
noDropGrader = graders.AssignmentFormatGrader("Homework", 12, 0)
|
||||
#Even though the minimum number is 3, this should grade correctly when 7 assignments are found
|
||||
overflowGrader = graders.AssignmentFormatGrader("Lab", 3, 2)
|
||||
labGrader = graders.AssignmentFormatGrader("Lab", 7, 3)
|
||||
|
||||
#Test the grading of an empty gradesheet
|
||||
for graded in [homeworkGrader.grade(self.empty_gradesheet),
|
||||
noDropGrader.grade(self.empty_gradesheet),
|
||||
homeworkGrader.grade(self.incomplete_gradesheet),
|
||||
noDropGrader.grade(self.incomplete_gradesheet)]:
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
#Make sure the breakdown includes 12 sections, plus one summary
|
||||
self.assertEqual(len(graded['section_breakdown']), 12 + 1)
|
||||
|
||||
graded = homeworkGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.11) # 100% + 10% / 10 assignments
|
||||
self.assertEqual(len(graded['section_breakdown']), 12 + 1)
|
||||
|
||||
graded = noDropGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.0916666666666666) # 100% + 10% / 12 assignments
|
||||
self.assertEqual(len(graded['section_breakdown']), 12 + 1)
|
||||
|
||||
graded = overflowGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.8880952380952382) # 100% + 10% / 5 assignments
|
||||
self.assertEqual(len(graded['section_breakdown']), 7 + 1)
|
||||
|
||||
graded = labGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.9226190476190477)
|
||||
self.assertEqual(len(graded['section_breakdown']), 7 + 1)
|
||||
|
||||
def test_WeightedSubsectionsGrader(self):
|
||||
#First, a few sub graders
|
||||
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
|
||||
labGrader = graders.AssignmentFormatGrader("Lab", 7, 3)
|
||||
midtermGrader = graders.SingleSectionGrader("Midterm", "Midterm Exam")
|
||||
|
||||
weightedGrader = graders.WeightedSubsectionsGrader([(homeworkGrader, homeworkGrader.category, 0.25), (labGrader, labGrader.category, 0.25),
|
||||
(midtermGrader, midtermGrader.category, 0.5)])
|
||||
|
||||
overOneWeightsGrader = graders.WeightedSubsectionsGrader([(homeworkGrader, homeworkGrader.category, 0.5), (labGrader, labGrader.category, 0.5),
|
||||
(midtermGrader, midtermGrader.category, 0.5)])
|
||||
|
||||
#The midterm should have all weight on this one
|
||||
zeroWeightsGrader = graders.WeightedSubsectionsGrader([(homeworkGrader, homeworkGrader.category, 0.0), (labGrader, labGrader.category, 0.0),
|
||||
(midtermGrader, midtermGrader.category, 0.5)])
|
||||
|
||||
#This should always have a final percent of zero
|
||||
allZeroWeightsGrader = graders.WeightedSubsectionsGrader([(homeworkGrader, homeworkGrader.category, 0.0), (labGrader, labGrader.category, 0.0),
|
||||
(midtermGrader, midtermGrader.category, 0.0)])
|
||||
|
||||
emptyGrader = graders.WeightedSubsectionsGrader([])
|
||||
|
||||
graded = weightedGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.5106547619047619)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = overOneWeightsGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.7688095238095238)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = zeroWeightsGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.2525)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = allZeroWeightsGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
for graded in [weightedGrader.grade(self.empty_gradesheet),
|
||||
weightedGrader.grade(self.incomplete_gradesheet),
|
||||
zeroWeightsGrader.grade(self.empty_gradesheet),
|
||||
allZeroWeightsGrader.grade(self.empty_gradesheet)]:
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = emptyGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
self.assertEqual(len(graded['section_breakdown']), 0)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 0)
|
||||
|
||||
def test_graderFromConf(self):
|
||||
|
||||
#Confs always produce a graders.WeightedSubsectionsGrader, so we test this by repeating the test
|
||||
#in test_graders.WeightedSubsectionsGrader, but generate the graders with confs.
|
||||
|
||||
weightedGrader = graders.grader_from_conf([
|
||||
{
|
||||
'type': "Homework",
|
||||
'min_count': 12,
|
||||
'drop_count': 2,
|
||||
'short_label': "HW",
|
||||
'weight': 0.25,
|
||||
},
|
||||
{
|
||||
'type': "Lab",
|
||||
'min_count': 7,
|
||||
'drop_count': 3,
|
||||
'category': "Labs",
|
||||
'weight': 0.25
|
||||
},
|
||||
{
|
||||
'type': "Midterm",
|
||||
'name': "Midterm Exam",
|
||||
'short_label': "Midterm",
|
||||
'weight': 0.5,
|
||||
},
|
||||
])
|
||||
|
||||
emptyGrader = graders.grader_from_conf([])
|
||||
|
||||
graded = weightedGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.5106547619047619)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = emptyGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
self.assertEqual(len(graded['section_breakdown']), 0)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 0)
|
||||
|
||||
#Test that graders can also be used instead of lists of dictionaries
|
||||
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
|
||||
homeworkGrader2 = graders.grader_from_conf(homeworkGrader)
|
||||
|
||||
graded = homeworkGrader2.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.11)
|
||||
self.assertEqual(len(graded['section_breakdown']), 12 + 1)
|
||||
|
||||
#TODO: How do we test failure cases? The parser only logs an error when it can't parse something. Maybe it should throw exceptions?
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Module progress tests
|
||||
|
||||
|
||||
class ProgressTest(unittest.TestCase):
|
||||
''' Test that basic Progress objects work. A Progress represents a
|
||||
fraction between 0 and 1.
|
||||
'''
|
||||
not_started = Progress(0, 17)
|
||||
part_done = Progress(2, 6)
|
||||
half_done = Progress(3, 6)
|
||||
also_half_done = Progress(1, 2)
|
||||
done = Progress(7, 7)
|
||||
|
||||
def test_create_object(self):
|
||||
# These should work:
|
||||
p = Progress(0, 2)
|
||||
p = Progress(1, 2)
|
||||
p = Progress(2, 2)
|
||||
|
||||
p = Progress(2.5, 5.0)
|
||||
p = Progress(3.7, 12.3333)
|
||||
|
||||
# These shouldn't
|
||||
self.assertRaises(ValueError, Progress, 0, 0)
|
||||
self.assertRaises(ValueError, Progress, 2, 0)
|
||||
self.assertRaises(ValueError, Progress, 1, -2)
|
||||
|
||||
self.assertRaises(TypeError, Progress, 0, "all")
|
||||
# check complex numbers just for the heck of it :)
|
||||
self.assertRaises(TypeError, Progress, 2j, 3)
|
||||
|
||||
def test_clamp(self):
|
||||
self.assertEqual((2, 2), Progress(3, 2).frac())
|
||||
self.assertEqual((0, 2), Progress(-2, 2).frac())
|
||||
|
||||
def test_frac(self):
|
||||
p = Progress(1, 2)
|
||||
(a, b) = p.frac()
|
||||
self.assertEqual(a, 1)
|
||||
self.assertEqual(b, 2)
|
||||
|
||||
def test_percent(self):
|
||||
self.assertEqual(self.not_started.percent(), 0)
|
||||
self.assertAlmostEqual(self.part_done.percent(), 33.33333333333333)
|
||||
self.assertEqual(self.half_done.percent(), 50)
|
||||
self.assertEqual(self.done.percent(), 100)
|
||||
|
||||
self.assertEqual(self.half_done.percent(), self.also_half_done.percent())
|
||||
|
||||
def test_started(self):
|
||||
self.assertFalse(self.not_started.started())
|
||||
|
||||
self.assertTrue(self.part_done.started())
|
||||
self.assertTrue(self.half_done.started())
|
||||
self.assertTrue(self.done.started())
|
||||
|
||||
def test_inprogress(self):
|
||||
# only true if working on it
|
||||
self.assertFalse(self.done.inprogress())
|
||||
self.assertFalse(self.not_started.inprogress())
|
||||
|
||||
self.assertTrue(self.part_done.inprogress())
|
||||
self.assertTrue(self.half_done.inprogress())
|
||||
|
||||
def test_done(self):
|
||||
self.assertTrue(self.done.done())
|
||||
self.assertFalse(self.half_done.done())
|
||||
self.assertFalse(self.not_started.done())
|
||||
|
||||
def test_str(self):
|
||||
self.assertEqual(str(self.not_started), "0/17")
|
||||
self.assertEqual(str(self.part_done), "2/6")
|
||||
self.assertEqual(str(self.done), "7/7")
|
||||
|
||||
def test_ternary_str(self):
|
||||
self.assertEqual(self.not_started.ternary_str(), "none")
|
||||
self.assertEqual(self.half_done.ternary_str(), "in_progress")
|
||||
self.assertEqual(self.done.ternary_str(), "done")
|
||||
|
||||
def test_to_js_status(self):
|
||||
'''Test the Progress.to_js_status_str() method'''
|
||||
|
||||
self.assertEqual(Progress.to_js_status_str(self.not_started), "none")
|
||||
self.assertEqual(Progress.to_js_status_str(self.half_done), "in_progress")
|
||||
self.assertEqual(Progress.to_js_status_str(self.done), "done")
|
||||
self.assertEqual(Progress.to_js_status_str(None), "NA")
|
||||
|
||||
def test_to_js_detail_str(self):
|
||||
'''Test the Progress.to_js_detail_str() method'''
|
||||
f = Progress.to_js_detail_str
|
||||
for p in (self.not_started, self.half_done, self.done):
|
||||
self.assertEqual(f(p), str(p))
|
||||
# But None should be encoded as NA
|
||||
self.assertEqual(f(None), "NA")
|
||||
|
||||
def test_add(self):
|
||||
'''Test the Progress.add_counts() method'''
|
||||
p = Progress(0, 2)
|
||||
p2 = Progress(1, 3)
|
||||
p3 = Progress(2, 5)
|
||||
pNone = None
|
||||
add = lambda a, b: Progress.add_counts(a, b).frac()
|
||||
|
||||
self.assertEqual(add(p, p), (0, 4))
|
||||
self.assertEqual(add(p, p2), (1, 5))
|
||||
self.assertEqual(add(p2, p3), (3, 8))
|
||||
|
||||
self.assertEqual(add(p2, pNone), p2.frac())
|
||||
self.assertEqual(add(pNone, p2), p2.frac())
|
||||
|
||||
def test_equality(self):
|
||||
'''Test that comparing Progress objects for equality
|
||||
works correctly.'''
|
||||
p = Progress(1, 2)
|
||||
p2 = Progress(2, 4)
|
||||
p3 = Progress(1, 2)
|
||||
self.assertTrue(p == p3)
|
||||
self.assertFalse(p == p2)
|
||||
|
||||
# Check != while we're at it
|
||||
self.assertTrue(p != p2)
|
||||
self.assertFalse(p != p3)
|
||||
|
||||
|
||||
class ModuleProgressTest(unittest.TestCase):
|
||||
''' Test that get_progress() does the right thing for the different modules
|
||||
'''
|
||||
def test_xmodule_default(self):
|
||||
'''Make sure default get_progress exists, returns None'''
|
||||
xm = x_module.XModule(i4xs, 'a://b/c/d/e', None, {})
|
||||
p = xm.get_progress()
|
||||
self.assertEqual(p, None)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil One.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Two.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true">
|
||||
<startouttext />This is foil Three.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Four.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Five.<endouttext />
|
||||
</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil One.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Two.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true">
|
||||
<startouttext />This is foil Three.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true">
|
||||
<startouttext />This is foil Four.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Five.<endouttext />
|
||||
</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil One.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Two.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true">
|
||||
<startouttext />This is foil Three.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true">
|
||||
<startouttext />This is foil Four.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Five.<endouttext />
|
||||
</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
@@ -1,40 +0,0 @@
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<radiogroup>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil One.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Two.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true">
|
||||
<startouttext />This is foil Three.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Four.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Five.<endouttext />
|
||||
</choice>
|
||||
</radiogroup>
|
||||
</choiceresponse>
|
||||
<choiceresponse>
|
||||
<radiogroup>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil One.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Two.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true">
|
||||
<startouttext />This is foil Three.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true">
|
||||
<startouttext />This is foil Four.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Five.<endouttext />
|
||||
</choice>
|
||||
</radiogroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
@@ -1,33 +0,0 @@
|
||||
<problem>
|
||||
<text>
|
||||
<h2>Code response</h2>
|
||||
|
||||
<p>
|
||||
</p>
|
||||
|
||||
<text>
|
||||
Write a program to compute the square of a number
|
||||
<coderesponse tests="repeat:2,generate">
|
||||
<textbox rows="10" cols="70" mode="python"/>
|
||||
<codeparam>
|
||||
<initial_display>def square(x):</initial_display>
|
||||
<answer_display>answer</answer_display>
|
||||
<grader_payload>grader stuff</grader_payload>
|
||||
</codeparam>
|
||||
</coderesponse>
|
||||
</text>
|
||||
|
||||
<text>
|
||||
Write a program to compute the square of a number
|
||||
<coderesponse tests="repeat:2,generate">
|
||||
<textbox rows="10" cols="70" mode="python"/>
|
||||
<codeparam>
|
||||
<initial_display>def square(x):</initial_display>
|
||||
<answer_display>answer</answer_display>
|
||||
<grader_payload>grader stuff</grader_payload>
|
||||
</codeparam>
|
||||
</coderesponse>
|
||||
</text>
|
||||
|
||||
</text>
|
||||
</problem>
|
||||
@@ -1,101 +0,0 @@
|
||||
<problem>
|
||||
<text>
|
||||
<h2>Code response</h2>
|
||||
|
||||
<p>
|
||||
</p>
|
||||
|
||||
<text>
|
||||
Write a program to compute the square of a number
|
||||
<coderesponse tests="repeat:2,generate">
|
||||
<textbox rows="10" cols="70" mode="python"/>
|
||||
<answer><![CDATA[
|
||||
initial_display = """
|
||||
def square(n):
|
||||
"""
|
||||
|
||||
answer = """
|
||||
def square(n):
|
||||
return n**2
|
||||
"""
|
||||
|
||||
preamble = """
|
||||
import sys, time
|
||||
"""
|
||||
|
||||
test_program = """
|
||||
import random
|
||||
import operator
|
||||
|
||||
def testSquare(n = None):
|
||||
if n is None:
|
||||
n = random.randint(2, 20)
|
||||
print 'Test is: square(%d)'%n
|
||||
return str(square(n))
|
||||
|
||||
def main():
|
||||
f = os.fdopen(3,'w')
|
||||
test = int(sys.argv[1])
|
||||
rndlist = map(int,os.getenv('rndlist').split(','))
|
||||
random.seed(rndlist[0])
|
||||
if test == 1: f.write(testSquare(0))
|
||||
elif test == 2: f.write(testSquare(1))
|
||||
else: f.write(testSquare())
|
||||
f.close()
|
||||
|
||||
main()
|
||||
sys.exit(0)
|
||||
"""
|
||||
]]>
|
||||
</answer>
|
||||
</coderesponse>
|
||||
</text>
|
||||
|
||||
<text>
|
||||
Write a program to compute the cube of a number
|
||||
<coderesponse tests="repeat:2,generate">
|
||||
<textbox rows="10" cols="70" mode="python"/>
|
||||
<answer><![CDATA[
|
||||
initial_display = """
|
||||
def cube(n):
|
||||
"""
|
||||
|
||||
answer = """
|
||||
def cube(n):
|
||||
return n**3
|
||||
"""
|
||||
|
||||
preamble = """
|
||||
import sys, time
|
||||
"""
|
||||
|
||||
test_program = """
|
||||
import random
|
||||
import operator
|
||||
|
||||
def testCube(n = None):
|
||||
if n is None:
|
||||
n = random.randint(2, 20)
|
||||
print 'Test is: cube(%d)'%n
|
||||
return str(cube(n))
|
||||
|
||||
def main():
|
||||
f = os.fdopen(3,'w')
|
||||
test = int(sys.argv[1])
|
||||
rndlist = map(int,os.getenv('rndlist').split(','))
|
||||
random.seed(rndlist[0])
|
||||
if test == 1: f.write(testCube(0))
|
||||
elif test == 2: f.write(testCube(1))
|
||||
else: f.write(testCube())
|
||||
f.close()
|
||||
|
||||
main()
|
||||
sys.exit(0)
|
||||
"""
|
||||
]]>
|
||||
</answer>
|
||||
</coderesponse>
|
||||
</text>
|
||||
|
||||
</text>
|
||||
</problem>
|
||||
@@ -1,45 +0,0 @@
|
||||
<problem>
|
||||
<script type="loncapa/python">
|
||||
# from loncapa import *
|
||||
x1 = 4 # lc_random(2,4,1)
|
||||
y1 = 5 # lc_random(3,7,1)
|
||||
|
||||
x2 = 10 # lc_random(x1+1,9,1)
|
||||
y2 = 20 # lc_random(y1+1,15,1)
|
||||
|
||||
m = (y2-y1)/(x2-x1)
|
||||
b = y1 - m*x1
|
||||
answer = "%s*x+%s" % (m,b)
|
||||
answer = answer.replace('+-','-')
|
||||
|
||||
inverted_m = (x2-x1)/(y2-y1)
|
||||
inverted_b = b
|
||||
wrongans = "%s*x+%s" % (inverted_m,inverted_b)
|
||||
wrongans = wrongans.replace('+-','-')
|
||||
</script>
|
||||
|
||||
<text>
|
||||
<p>Hints can be provided to students, based on the last response given, as well as the history of responses given. Here is an example of a hint produced by a Formula Response problem.</p>
|
||||
|
||||
<p>
|
||||
What is the equation of the line which passess through ($x1,$y1) and
|
||||
($x2,$y2)?</p>
|
||||
|
||||
<p>The correct answer is <tt>$answer</tt>. A common error is to invert the equation for the slope. Enter <tt>
|
||||
$wrongans</tt> to see a hint.</p>
|
||||
|
||||
</text>
|
||||
|
||||
<formularesponse samples="x@-5:5#11" id="11" answer="$answer">
|
||||
<responseparam description="Numerical Tolerance" type="tolerance" default="0.001" name="tol" />
|
||||
<text>y = <textline size="25" /></text>
|
||||
<hintgroup>
|
||||
<formulahint samples="x@-5:5#11" answer="$wrongans" name="inversegrad">
|
||||
</formulahint>
|
||||
<hintpart on="inversegrad">
|
||||
<text>You have inverted the slope in the question.</text>
|
||||
</hintpart>
|
||||
</hintgroup>
|
||||
</formularesponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<problem>
|
||||
<text><p>
|
||||
Two skiers are on frictionless black diamond ski slopes.
|
||||
Hello</p></text>
|
||||
|
||||
<imageresponse max="1" loncapaid="11">
|
||||
<imageinput src="/static/Physics801/Figures/Skier-conservation of energy.jpg" width="560" height="388" rectangle="(490,11)-(556,98)"/>
|
||||
<text>Click on the image where the top skier will stop momentarily if the top skier starts from rest.</text>
|
||||
<imageinput src="/static/Physics801/Figures/Skier-conservation of energy.jpg" width="560" height="388" rectangle="(242,202)-(296,276)"/>
|
||||
<text>Click on the image where the lower skier will stop momentarily if the lower skier starts from rest.</text>
|
||||
<hintgroup showoncorrect="no">
|
||||
<text><p>Use conservation of energy.</p></text>
|
||||
</hintgroup>
|
||||
</imageresponse>
|
||||
</problem>
|
||||
@@ -1,13 +0,0 @@
|
||||
<problem>
|
||||
|
||||
<javascriptresponse>
|
||||
<generator src="test_problem_generator.js"/>
|
||||
<grader src="test_problem_grader.js"/>
|
||||
<display class="TestProblemDisplay" src="test_problem_display.js"/>
|
||||
<responseparam name="value" value="4"/>
|
||||
<javascriptinput>
|
||||
</javascriptinput>
|
||||
</javascriptresponse>
|
||||
|
||||
</problem>
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
// Generated by CoffeeScript 1.3.3
|
||||
(function() {
|
||||
var MinimaxProblemDisplay, root,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
MinimaxProblemDisplay = (function(_super) {
|
||||
|
||||
__extends(MinimaxProblemDisplay, _super);
|
||||
|
||||
function MinimaxProblemDisplay(state, submission, evaluation, container, submissionField, parameters) {
|
||||
this.state = state;
|
||||
this.submission = submission;
|
||||
this.evaluation = evaluation;
|
||||
this.container = container;
|
||||
this.submissionField = submissionField;
|
||||
this.parameters = parameters != null ? parameters : {};
|
||||
MinimaxProblemDisplay.__super__.constructor.call(this, this.state, this.submission, this.evaluation, this.container, this.submissionField, this.parameters);
|
||||
}
|
||||
|
||||
MinimaxProblemDisplay.prototype.render = function() {};
|
||||
|
||||
MinimaxProblemDisplay.prototype.createSubmission = function() {
|
||||
var id, value, _ref, _results;
|
||||
this.newSubmission = {};
|
||||
if (this.submission != null) {
|
||||
_ref = this.submission;
|
||||
_results = [];
|
||||
for (id in _ref) {
|
||||
value = _ref[id];
|
||||
_results.push(this.newSubmission[id] = value);
|
||||
}
|
||||
return _results;
|
||||
}
|
||||
};
|
||||
|
||||
MinimaxProblemDisplay.prototype.getCurrentSubmission = function() {
|
||||
return this.newSubmission;
|
||||
};
|
||||
|
||||
return MinimaxProblemDisplay;
|
||||
|
||||
})(XProblemDisplay);
|
||||
|
||||
root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
|
||||
root.TestProblemDisplay = TestProblemDisplay;
|
||||
|
||||
}).call(this);
|
||||
;
|
||||
@@ -1,50 +0,0 @@
|
||||
// Generated by CoffeeScript 1.3.3
|
||||
(function() {
|
||||
var MinimaxProblemDisplay, root,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
MinimaxProblemDisplay = (function(_super) {
|
||||
|
||||
__extends(MinimaxProblemDisplay, _super);
|
||||
|
||||
function MinimaxProblemDisplay(state, submission, evaluation, container, submissionField, parameters) {
|
||||
this.state = state;
|
||||
this.submission = submission;
|
||||
this.evaluation = evaluation;
|
||||
this.container = container;
|
||||
this.submissionField = submissionField;
|
||||
this.parameters = parameters != null ? parameters : {};
|
||||
MinimaxProblemDisplay.__super__.constructor.call(this, this.state, this.submission, this.evaluation, this.container, this.submissionField, this.parameters);
|
||||
}
|
||||
|
||||
MinimaxProblemDisplay.prototype.render = function() {};
|
||||
|
||||
MinimaxProblemDisplay.prototype.createSubmission = function() {
|
||||
var id, value, _ref, _results;
|
||||
this.newSubmission = {};
|
||||
if (this.submission != null) {
|
||||
_ref = this.submission;
|
||||
_results = [];
|
||||
for (id in _ref) {
|
||||
value = _ref[id];
|
||||
_results.push(this.newSubmission[id] = value);
|
||||
}
|
||||
return _results;
|
||||
}
|
||||
};
|
||||
|
||||
MinimaxProblemDisplay.prototype.getCurrentSubmission = function() {
|
||||
return this.newSubmission;
|
||||
};
|
||||
|
||||
return MinimaxProblemDisplay;
|
||||
|
||||
})(XProblemDisplay);
|
||||
|
||||
root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
|
||||
root.TestProblemDisplay = TestProblemDisplay;
|
||||
|
||||
}).call(this);
|
||||
;
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
I've wrapped Makoto Matsumoto and Takuji Nishimura's code in a namespace
|
||||
so it's better encapsulated. Now you can have multiple random number generators
|
||||
and they won't stomp all over eachother's state.
|
||||
|
||||
If you want to use this as a substitute for Math.random(), use the random()
|
||||
method like so:
|
||||
|
||||
var m = new MersenneTwister();
|
||||
var randomNumber = m.random();
|
||||
|
||||
You can also call the other genrand_{foo}() methods on the instance.
|
||||
|
||||
If you want to use a specific seed in order to get a repeatable random
|
||||
sequence, pass an integer into the constructor:
|
||||
|
||||
var m = new MersenneTwister(123);
|
||||
|
||||
and that will always produce the same random sequence.
|
||||
|
||||
Sean McCullough (banksean@gmail.com)
|
||||
*/
|
||||
|
||||
/*
|
||||
A C-program for MT19937, with initialization improved 2002/1/26.
|
||||
Coded by Takuji Nishimura and Makoto Matsumoto.
|
||||
|
||||
Before using, initialize the state by using init_genrand(seed)
|
||||
or init_by_array(init_key, key_length).
|
||||
|
||||
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of its contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Any feedback is very welcome.
|
||||
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
|
||||
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
|
||||
*/
|
||||
|
||||
var MersenneTwister = function(seed) {
|
||||
if (seed == undefined) {
|
||||
seed = new Date().getTime();
|
||||
}
|
||||
/* Period parameters */
|
||||
this.N = 624;
|
||||
this.M = 397;
|
||||
this.MATRIX_A = 0x9908b0df; /* constant vector a */
|
||||
this.UPPER_MASK = 0x80000000; /* most significant w-r bits */
|
||||
this.LOWER_MASK = 0x7fffffff; /* least significant r bits */
|
||||
|
||||
this.mt = new Array(this.N); /* the array for the state vector */
|
||||
this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */
|
||||
|
||||
this.init_genrand(seed);
|
||||
}
|
||||
|
||||
/* initializes mt[N] with a seed */
|
||||
MersenneTwister.prototype.init_genrand = function(s) {
|
||||
this.mt[0] = s >>> 0;
|
||||
for (this.mti=1; this.mti<this.N; this.mti++) {
|
||||
var s = this.mt[this.mti-1] ^ (this.mt[this.mti-1] >>> 30);
|
||||
this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
|
||||
+ this.mti;
|
||||
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
|
||||
/* In the previous versions, MSBs of the seed affect */
|
||||
/* only MSBs of the array mt[]. */
|
||||
/* 2002/01/09 modified by Makoto Matsumoto */
|
||||
this.mt[this.mti] >>>= 0;
|
||||
/* for >32 bit machines */
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize by an array with array-length */
|
||||
/* init_key is the array for initializing keys */
|
||||
/* key_length is its length */
|
||||
/* slight change for C++, 2004/2/26 */
|
||||
MersenneTwister.prototype.init_by_array = function(init_key, key_length) {
|
||||
var i, j, k;
|
||||
this.init_genrand(19650218);
|
||||
i=1; j=0;
|
||||
k = (this.N>key_length ? this.N : key_length);
|
||||
for (; k; k--) {
|
||||
var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30)
|
||||
this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
|
||||
+ init_key[j] + j; /* non linear */
|
||||
this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
|
||||
i++; j++;
|
||||
if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
|
||||
if (j>=key_length) j=0;
|
||||
}
|
||||
for (k=this.N-1; k; k--) {
|
||||
var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30);
|
||||
this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
|
||||
- i; /* non linear */
|
||||
this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
|
||||
i++;
|
||||
if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
|
||||
}
|
||||
|
||||
this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
|
||||
}
|
||||
|
||||
/* generates a random number on [0,0xffffffff]-interval */
|
||||
MersenneTwister.prototype.genrand_int32 = function() {
|
||||
var y;
|
||||
var mag01 = new Array(0x0, this.MATRIX_A);
|
||||
/* mag01[x] = x * MATRIX_A for x=0,1 */
|
||||
|
||||
if (this.mti >= this.N) { /* generate N words at one time */
|
||||
var kk;
|
||||
|
||||
if (this.mti == this.N+1) /* if init_genrand() has not been called, */
|
||||
this.init_genrand(5489); /* a default initial seed is used */
|
||||
|
||||
for (kk=0;kk<this.N-this.M;kk++) {
|
||||
y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
|
||||
this.mt[kk] = this.mt[kk+this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
}
|
||||
for (;kk<this.N-1;kk++) {
|
||||
y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
|
||||
this.mt[kk] = this.mt[kk+(this.M-this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
}
|
||||
y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK);
|
||||
this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
|
||||
this.mti = 0;
|
||||
}
|
||||
|
||||
y = this.mt[this.mti++];
|
||||
|
||||
/* Tempering */
|
||||
y ^= (y >>> 11);
|
||||
y ^= (y << 7) & 0x9d2c5680;
|
||||
y ^= (y << 15) & 0xefc60000;
|
||||
y ^= (y >>> 18);
|
||||
|
||||
return y >>> 0;
|
||||
}
|
||||
|
||||
/* generates a random number on [0,0x7fffffff]-interval */
|
||||
MersenneTwister.prototype.genrand_int31 = function() {
|
||||
return (this.genrand_int32()>>>1);
|
||||
}
|
||||
|
||||
/* generates a random number on [0,1]-real-interval */
|
||||
MersenneTwister.prototype.genrand_real1 = function() {
|
||||
return this.genrand_int32()*(1.0/4294967295.0);
|
||||
/* divided by 2^32-1 */
|
||||
}
|
||||
|
||||
/* generates a random number on [0,1)-real-interval */
|
||||
MersenneTwister.prototype.random = function() {
|
||||
return this.genrand_int32()*(1.0/4294967296.0);
|
||||
/* divided by 2^32 */
|
||||
}
|
||||
|
||||
/* generates a random number on (0,1)-real-interval */
|
||||
MersenneTwister.prototype.genrand_real3 = function() {
|
||||
return (this.genrand_int32() + 0.5)*(1.0/4294967296.0);
|
||||
/* divided by 2^32 */
|
||||
}
|
||||
|
||||
/* generates a random number on [0,1) with 53-bit resolution*/
|
||||
MersenneTwister.prototype.genrand_res53 = function() {
|
||||
var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6;
|
||||
return(a*67108864.0+b)*(1.0/9007199254740992.0);
|
||||
}
|
||||
|
||||
/* These real versions are due to Isaku Wada, 2002/01/09 added */
|
||||
if(typeof exports == 'undefined'){
|
||||
var root = this;
|
||||
} else {
|
||||
var root = exports;
|
||||
}
|
||||
root.MersenneTwister = MersenneTwister;
|
||||
@@ -1,21 +0,0 @@
|
||||
class MinimaxProblemDisplay extends XProblemDisplay
|
||||
|
||||
constructor: (@state, @submission, @evaluation, @container, @submissionField, @parameters={}) ->
|
||||
|
||||
super(@state, @submission, @evaluation, @container, @submissionField, @parameters)
|
||||
|
||||
render: () ->
|
||||
|
||||
createSubmission: () ->
|
||||
|
||||
@newSubmission = {}
|
||||
|
||||
if @submission?
|
||||
for id, value of @submission
|
||||
@newSubmission[id] = value
|
||||
|
||||
getCurrentSubmission: () ->
|
||||
return @newSubmission
|
||||
|
||||
root = exports ? this
|
||||
root.TestProblemDisplay = TestProblemDisplay
|
||||
@@ -1,49 +0,0 @@
|
||||
// Generated by CoffeeScript 1.3.3
|
||||
(function() {
|
||||
var MinimaxProblemDisplay, root,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
MinimaxProblemDisplay = (function(_super) {
|
||||
|
||||
__extends(MinimaxProblemDisplay, _super);
|
||||
|
||||
function MinimaxProblemDisplay(state, submission, evaluation, container, submissionField, parameters) {
|
||||
this.state = state;
|
||||
this.submission = submission;
|
||||
this.evaluation = evaluation;
|
||||
this.container = container;
|
||||
this.submissionField = submissionField;
|
||||
this.parameters = parameters != null ? parameters : {};
|
||||
MinimaxProblemDisplay.__super__.constructor.call(this, this.state, this.submission, this.evaluation, this.container, this.submissionField, this.parameters);
|
||||
}
|
||||
|
||||
MinimaxProblemDisplay.prototype.render = function() {};
|
||||
|
||||
MinimaxProblemDisplay.prototype.createSubmission = function() {
|
||||
var id, value, _ref, _results;
|
||||
this.newSubmission = {};
|
||||
if (this.submission != null) {
|
||||
_ref = this.submission;
|
||||
_results = [];
|
||||
for (id in _ref) {
|
||||
value = _ref[id];
|
||||
_results.push(this.newSubmission[id] = value);
|
||||
}
|
||||
return _results;
|
||||
}
|
||||
};
|
||||
|
||||
MinimaxProblemDisplay.prototype.getCurrentSubmission = function() {
|
||||
return this.newSubmission;
|
||||
};
|
||||
|
||||
return MinimaxProblemDisplay;
|
||||
|
||||
})(XProblemDisplay);
|
||||
|
||||
root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
|
||||
root.TestProblemDisplay = TestProblemDisplay;
|
||||
|
||||
}).call(this);
|
||||
@@ -1,14 +0,0 @@
|
||||
class TestProblemGenerator extends XProblemGenerator
|
||||
|
||||
constructor: (seed, @parameters = {}) ->
|
||||
|
||||
super(seed, @parameters)
|
||||
|
||||
generate: () ->
|
||||
|
||||
@problemState.value = @parameters.value
|
||||
|
||||
return @problemState
|
||||
|
||||
root = exports ? this
|
||||
root.generatorClass = TestProblemGenerator
|
||||
@@ -1,29 +0,0 @@
|
||||
// Generated by CoffeeScript 1.3.3
|
||||
(function() {
|
||||
var TestProblemGenerator, root,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
TestProblemGenerator = (function(_super) {
|
||||
|
||||
__extends(TestProblemGenerator, _super);
|
||||
|
||||
function TestProblemGenerator(seed, parameters) {
|
||||
this.parameters = parameters != null ? parameters : {};
|
||||
TestProblemGenerator.__super__.constructor.call(this, seed, this.parameters);
|
||||
}
|
||||
|
||||
TestProblemGenerator.prototype.generate = function() {
|
||||
this.problemState.value = this.parameters.value;
|
||||
return this.problemState;
|
||||
};
|
||||
|
||||
return TestProblemGenerator;
|
||||
|
||||
})(XProblemGenerator);
|
||||
|
||||
root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
|
||||
root.generatorClass = TestProblemGenerator;
|
||||
|
||||
}).call(this);
|
||||
@@ -1,27 +0,0 @@
|
||||
class TestProblemGrader extends XProblemGrader
|
||||
|
||||
constructor: (@submission, @problemState, @parameters={}) ->
|
||||
|
||||
super(@submission, @problemState, @parameters)
|
||||
|
||||
solve: () ->
|
||||
|
||||
@solution = {0: @problemState.value}
|
||||
|
||||
grade: () ->
|
||||
|
||||
if not @solution?
|
||||
@solve()
|
||||
|
||||
allCorrect = true
|
||||
|
||||
for id, value of @solution
|
||||
valueCorrect = if @submission? then (value == @submission[id]) else false
|
||||
@evaluation[id] = valueCorrect
|
||||
if not valueCorrect
|
||||
allCorrect = false
|
||||
|
||||
return allCorrect
|
||||
|
||||
root = exports ? this
|
||||
root.graderClass = TestProblemGrader
|
||||
@@ -1,50 +0,0 @@
|
||||
// Generated by CoffeeScript 1.3.3
|
||||
(function() {
|
||||
var TestProblemGrader, root,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
TestProblemGrader = (function(_super) {
|
||||
|
||||
__extends(TestProblemGrader, _super);
|
||||
|
||||
function TestProblemGrader(submission, problemState, parameters) {
|
||||
this.submission = submission;
|
||||
this.problemState = problemState;
|
||||
this.parameters = parameters != null ? parameters : {};
|
||||
TestProblemGrader.__super__.constructor.call(this, this.submission, this.problemState, this.parameters);
|
||||
}
|
||||
|
||||
TestProblemGrader.prototype.solve = function() {
|
||||
return this.solution = {
|
||||
0: this.problemState.value
|
||||
};
|
||||
};
|
||||
|
||||
TestProblemGrader.prototype.grade = function() {
|
||||
var allCorrect, id, value, valueCorrect, _ref;
|
||||
if (!(this.solution != null)) {
|
||||
this.solve();
|
||||
}
|
||||
allCorrect = true;
|
||||
_ref = this.solution;
|
||||
for (id in _ref) {
|
||||
value = _ref[id];
|
||||
valueCorrect = this.submission != null ? value === this.submission[id] : false;
|
||||
this.evaluation[id] = valueCorrect;
|
||||
if (!valueCorrect) {
|
||||
allCorrect = false;
|
||||
}
|
||||
}
|
||||
return allCorrect;
|
||||
};
|
||||
|
||||
return TestProblemGrader;
|
||||
|
||||
})(XProblemGrader);
|
||||
|
||||
root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
|
||||
root.graderClass = TestProblemGrader;
|
||||
|
||||
}).call(this);
|
||||
@@ -1,47 +0,0 @@
|
||||
class XProblemGenerator
|
||||
|
||||
constructor: (seed, @parameters={}) ->
|
||||
|
||||
@random = new MersenneTwister(seed)
|
||||
|
||||
@problemState = {}
|
||||
|
||||
generate: () ->
|
||||
|
||||
console.error("Abstract method called: XProblemGenerator.generate")
|
||||
|
||||
class XProblemDisplay
|
||||
|
||||
constructor: (@state, @submission, @evaluation, @container, @submissionField, @parameters={}) ->
|
||||
|
||||
render: () ->
|
||||
|
||||
console.error("Abstract method called: XProblemDisplay.render")
|
||||
|
||||
updateSubmission: () ->
|
||||
|
||||
@submissionField.val(JSON.stringify(@getCurrentSubmission()))
|
||||
|
||||
getCurrentSubmission: () ->
|
||||
console.error("Abstract method called: XProblemDisplay.getCurrentSubmission")
|
||||
|
||||
class XProblemGrader
|
||||
|
||||
constructor: (@submission, @problemState, @parameters={}) ->
|
||||
|
||||
@solution = null
|
||||
@evaluation = {}
|
||||
|
||||
solve: () ->
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.solve")
|
||||
|
||||
grade: () ->
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.grade")
|
||||
|
||||
root = exports ? this
|
||||
|
||||
root.XProblemGenerator = XProblemGenerator
|
||||
root.XProblemDisplay = XProblemDisplay
|
||||
root.XProblemGrader = XProblemGrader
|
||||
@@ -1,78 +0,0 @@
|
||||
// Generated by CoffeeScript 1.3.3
|
||||
(function() {
|
||||
var XProblemDisplay, XProblemGenerator, XProblemGrader, root;
|
||||
|
||||
XProblemGenerator = (function() {
|
||||
|
||||
function XProblemGenerator(seed, parameters) {
|
||||
this.parameters = parameters != null ? parameters : {};
|
||||
this.random = new MersenneTwister(seed);
|
||||
this.problemState = {};
|
||||
}
|
||||
|
||||
XProblemGenerator.prototype.generate = function() {
|
||||
return console.error("Abstract method called: XProblemGenerator.generate");
|
||||
};
|
||||
|
||||
return XProblemGenerator;
|
||||
|
||||
})();
|
||||
|
||||
XProblemDisplay = (function() {
|
||||
|
||||
function XProblemDisplay(state, submission, evaluation, container, submissionField, parameters) {
|
||||
this.state = state;
|
||||
this.submission = submission;
|
||||
this.evaluation = evaluation;
|
||||
this.container = container;
|
||||
this.submissionField = submissionField;
|
||||
this.parameters = parameters != null ? parameters : {};
|
||||
}
|
||||
|
||||
XProblemDisplay.prototype.render = function() {
|
||||
return console.error("Abstract method called: XProblemDisplay.render");
|
||||
};
|
||||
|
||||
XProblemDisplay.prototype.updateSubmission = function() {
|
||||
return this.submissionField.val(JSON.stringify(this.getCurrentSubmission()));
|
||||
};
|
||||
|
||||
XProblemDisplay.prototype.getCurrentSubmission = function() {
|
||||
return console.error("Abstract method called: XProblemDisplay.getCurrentSubmission");
|
||||
};
|
||||
|
||||
return XProblemDisplay;
|
||||
|
||||
})();
|
||||
|
||||
XProblemGrader = (function() {
|
||||
|
||||
function XProblemGrader(submission, problemState, parameters) {
|
||||
this.submission = submission;
|
||||
this.problemState = problemState;
|
||||
this.parameters = parameters != null ? parameters : {};
|
||||
this.solution = null;
|
||||
this.evaluation = {};
|
||||
}
|
||||
|
||||
XProblemGrader.prototype.solve = function() {
|
||||
return console.error("Abstract method called: XProblemGrader.solve");
|
||||
};
|
||||
|
||||
XProblemGrader.prototype.grade = function() {
|
||||
return console.error("Abstract method called: XProblemGrader.grade");
|
||||
};
|
||||
|
||||
return XProblemGrader;
|
||||
|
||||
})();
|
||||
|
||||
root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
|
||||
root.XProblemGenerator = XProblemGenerator;
|
||||
|
||||
root.XProblemDisplay = XProblemDisplay;
|
||||
|
||||
root.XProblemGrader = XProblemGrader;
|
||||
|
||||
}).call(this);
|
||||
@@ -1,21 +0,0 @@
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup>
|
||||
<choice correct="false" >
|
||||
<startouttext />This is foil One.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false" >
|
||||
<startouttext />This is foil Two.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true" >
|
||||
<startouttext />This is foil Three.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Four.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false">
|
||||
<startouttext />This is foil Five.<endouttext />
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
@@ -1,21 +0,0 @@
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup>
|
||||
<choice correct="false" name="foil1">
|
||||
<startouttext />This is foil One.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false" name="foil2">
|
||||
<startouttext />This is foil Two.<endouttext />
|
||||
</choice>
|
||||
<choice correct="true" name="foil3">
|
||||
<startouttext />This is foil Three.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false" name="foil4">
|
||||
<startouttext />This is foil Four.<endouttext />
|
||||
</choice>
|
||||
<choice correct="false" name="foil5">
|
||||
<startouttext />This is foil Five.<endouttext />
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
@@ -1,63 +0,0 @@
|
||||
<problem>
|
||||
<text>
|
||||
<p>
|
||||
Why do bicycles benefit from having larger wheels when going up a bump as shown in the picture? <br/>
|
||||
Assume that for both bicycles:<br/>
|
||||
1.) The tires have equal air pressure.<br/>
|
||||
2.) The bicycles never leave the contact with the bump.<br/>
|
||||
3.) The bicycles have the same mass. The bicycle tires (regardless of size) have the same mass.<br/>
|
||||
</p>
|
||||
</text>
|
||||
<optionresponse texlayout="horizontal" max="10" randomize="yes">
|
||||
<ul>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels have more time to go over the bump. This decreases the magnitude of the force needed to lift the bicycle.</p>
|
||||
</text>
|
||||
<optioninput name="Foil1" location="random" options="('True','False')" correct="True">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels always have a smaller vertical displacement regardless of speed.</p>
|
||||
</text>
|
||||
<optioninput name="Foil2" location="random" options="('True','False')" correct="False">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels experience a force backward with less magnitude for the same amount of time.</p>
|
||||
</text>
|
||||
<optioninput name="Foil3" location="random" options="('True','False')" correct="False">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels experience a force backward with less magnitude for a greater amount of time.</p>
|
||||
</text>
|
||||
<optioninput name="Foil4" location="random" options="('True','False')" correct="True">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels have more kinetic energy turned into gravitational potential energy.</p>
|
||||
</text>
|
||||
<optioninput name="Foil5" location="random" options="('True','False')" correct="False">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels have more rotational kinetic energy, so the horizontal velocity of the biker changes less.</p>
|
||||
</text>
|
||||
<optioninput name="Foil6" location="random" options="('True','False')" correct="False">
|
||||
</optioninput>
|
||||
</li>
|
||||
</ul>
|
||||
<hintgroup showoncorrect="no">
|
||||
<text>
|
||||
<br/>
|
||||
<br/>
|
||||
</text>
|
||||
</hintgroup>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
@@ -1,25 +0,0 @@
|
||||
<problem >
|
||||
<text><h2>Example: String Response Problem</h2>
|
||||
<br/>
|
||||
</text>
|
||||
|
||||
<text>Which US state has Lansing as its capital?</text>
|
||||
<stringresponse answer="Michigan" type="ci">
|
||||
<textline size="20" />
|
||||
<hintgroup>
|
||||
<stringhint answer="wisconsin" type="cs" name="wisc">
|
||||
</stringhint>
|
||||
<stringhint answer="minnesota" type="cs" name="minn">
|
||||
</stringhint>
|
||||
<hintpart on="wisc">
|
||||
<text>The state capital of Wisconsin is Madison.</text>
|
||||
</hintpart>
|
||||
<hintpart on="minn">
|
||||
<text>The state capital of Minnesota is St. Paul.</text>
|
||||
</hintpart>
|
||||
<hintpart on="default">
|
||||
<text>The state you are looking for is also known as the 'Great Lakes State'</text>
|
||||
</hintpart>
|
||||
</hintgroup>
|
||||
</stringresponse>
|
||||
</problem>
|
||||
@@ -1,29 +0,0 @@
|
||||
<problem>
|
||||
<text>
|
||||
<h2>Example: Symbolic Math Response Problem</h2>
|
||||
|
||||
<p>
|
||||
A symbolic math response problem presents one or more symbolic math
|
||||
input fields for input. Correctness of input is evaluated based on
|
||||
the symbolic properties of the expression entered. The student enters
|
||||
text, but sees a proper symbolic rendition of the entered formula, in
|
||||
real time, next to the input box.
|
||||
</p>
|
||||
|
||||
<p>This is a correct answer which may be entered below: </p>
|
||||
<p><tt>cos(theta)*[[1,0],[0,1]] + i*sin(theta)*[[0,1],[1,0]]</tt></p>
|
||||
|
||||
<script>
|
||||
from symmath import *
|
||||
</script>
|
||||
<text>Compute [mathjax] U = \exp\left( i \theta \left[ \begin{matrix} 0 & 1 \\ 1 & 0 \end{matrix} \right] \right) [/mathjax]
|
||||
and give the resulting \(2 \times 2\) matrix. <br/>
|
||||
Your input should be typed in as a list of lists, eg <tt>[[1,2],[3,4]]</tt>. <br/>
|
||||
[mathjax]U=[/mathjax] <symbolicresponse cfn="symmath_check" answer="[[cos(theta),I*sin(theta)],[I*sin(theta),cos(theta)]]" options="matrix,imaginaryi" id="filenamedogi0VpEBOWedxsymmathresponse_1" state="unsubmitted">
|
||||
<textline size="80" math="1" response_id="2" answer_id="1" id="filenamedogi0VpEBOWedxsymmathresponse_2_1"/>
|
||||
</symbolicresponse>
|
||||
<br/>
|
||||
</text>
|
||||
|
||||
</text>
|
||||
</problem>
|
||||
@@ -1,21 +0,0 @@
|
||||
<problem>
|
||||
<truefalseresponse max="10" randomize="yes">
|
||||
<choicegroup>
|
||||
<choice location="random" correct="true" name="foil1">
|
||||
<startouttext />This is foil One.<endouttext />
|
||||
</choice>
|
||||
<choice location="random" correct="true" name="foil2">
|
||||
<startouttext />This is foil Two.<endouttext />
|
||||
</choice>
|
||||
<choice location="random" correct="false" name="foil3">
|
||||
<startouttext />This is foil Three.<endouttext />
|
||||
</choice>
|
||||
<choice location="random" correct="false" name="foil4">
|
||||
<startouttext />This is foil Four.<endouttext />
|
||||
</choice>
|
||||
<choice location="random" correct="false" name="foil5">
|
||||
<startouttext />This is foil Five.<endouttext />
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</truefalseresponse>
|
||||
</problem>
|
||||
220
common/lib/xmodule/xmodule/tests/test_graders.py
Normal file
220
common/lib/xmodule/xmodule/tests/test_graders.py
Normal file
@@ -0,0 +1,220 @@
|
||||
"""Grading tests"""
|
||||
import unittest
|
||||
|
||||
from xmodule import graders
|
||||
from xmodule.graders import Score, aggregate_scores
|
||||
|
||||
class GradesheetTest(unittest.TestCase):
|
||||
|
||||
def test_weighted_grading(self):
|
||||
scores = []
|
||||
Score.__sub__ = lambda me, other: (me.earned - other.earned) + (me.possible - other.possible)
|
||||
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertEqual(all, Score(earned=0, possible=0, graded=False, section="summary"))
|
||||
self.assertEqual(graded, Score(earned=0, possible=0, graded=True, section="summary"))
|
||||
|
||||
scores.append(Score(earned=0, possible=5, graded=False, section="summary"))
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertEqual(all, Score(earned=0, possible=5, graded=False, section="summary"))
|
||||
self.assertEqual(graded, Score(earned=0, possible=0, graded=True, section="summary"))
|
||||
|
||||
scores.append(Score(earned=3, possible=5, graded=True, section="summary"))
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertAlmostEqual(all, Score(earned=3, possible=10, graded=False, section="summary"))
|
||||
self.assertAlmostEqual(graded, Score(earned=3, possible=5, graded=True, section="summary"))
|
||||
|
||||
scores.append(Score(earned=2, possible=5, graded=True, section="summary"))
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertAlmostEqual(all, Score(earned=5, possible=15, graded=False, section="summary"))
|
||||
self.assertAlmostEqual(graded, Score(earned=5, possible=10, graded=True, section="summary"))
|
||||
|
||||
|
||||
class GraderTest(unittest.TestCase):
|
||||
|
||||
empty_gradesheet = {
|
||||
}
|
||||
|
||||
incomplete_gradesheet = {
|
||||
'Homework': [],
|
||||
'Lab': [],
|
||||
'Midterm': [],
|
||||
}
|
||||
|
||||
test_gradesheet = {
|
||||
'Homework': [Score(earned=2, possible=20.0, graded=True, section='hw1'),
|
||||
Score(earned=16, possible=16.0, graded=True, section='hw2')],
|
||||
#The dropped scores should be from the assignments that don't exist yet
|
||||
|
||||
'Lab': [Score(earned=1, possible=2.0, graded=True, section='lab1'), # Dropped
|
||||
Score(earned=1, possible=1.0, graded=True, section='lab2'),
|
||||
Score(earned=1, possible=1.0, graded=True, section='lab3'),
|
||||
Score(earned=5, possible=25.0, graded=True, section='lab4'), # Dropped
|
||||
Score(earned=3, possible=4.0, graded=True, section='lab5'), # Dropped
|
||||
Score(earned=6, possible=7.0, graded=True, section='lab6'),
|
||||
Score(earned=5, possible=6.0, graded=True, section='lab7')],
|
||||
|
||||
'Midterm': [Score(earned=50.5, possible=100, graded=True, section="Midterm Exam"), ],
|
||||
}
|
||||
|
||||
def test_SingleSectionGrader(self):
|
||||
midtermGrader = graders.SingleSectionGrader("Midterm", "Midterm Exam")
|
||||
lab4Grader = graders.SingleSectionGrader("Lab", "lab4")
|
||||
badLabGrader = graders.SingleSectionGrader("Lab", "lab42")
|
||||
|
||||
for graded in [midtermGrader.grade(self.empty_gradesheet),
|
||||
midtermGrader.grade(self.incomplete_gradesheet),
|
||||
badLabGrader.grade(self.test_gradesheet)]:
|
||||
self.assertEqual(len(graded['section_breakdown']), 1)
|
||||
self.assertEqual(graded['percent'], 0.0)
|
||||
|
||||
graded = midtermGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.505)
|
||||
self.assertEqual(len(graded['section_breakdown']), 1)
|
||||
|
||||
graded = lab4Grader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.2)
|
||||
self.assertEqual(len(graded['section_breakdown']), 1)
|
||||
|
||||
def test_AssignmentFormatGrader(self):
|
||||
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
|
||||
noDropGrader = graders.AssignmentFormatGrader("Homework", 12, 0)
|
||||
#Even though the minimum number is 3, this should grade correctly when 7 assignments are found
|
||||
overflowGrader = graders.AssignmentFormatGrader("Lab", 3, 2)
|
||||
labGrader = graders.AssignmentFormatGrader("Lab", 7, 3)
|
||||
|
||||
#Test the grading of an empty gradesheet
|
||||
for graded in [homeworkGrader.grade(self.empty_gradesheet),
|
||||
noDropGrader.grade(self.empty_gradesheet),
|
||||
homeworkGrader.grade(self.incomplete_gradesheet),
|
||||
noDropGrader.grade(self.incomplete_gradesheet)]:
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
#Make sure the breakdown includes 12 sections, plus one summary
|
||||
self.assertEqual(len(graded['section_breakdown']), 12 + 1)
|
||||
|
||||
graded = homeworkGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.11) # 100% + 10% / 10 assignments
|
||||
self.assertEqual(len(graded['section_breakdown']), 12 + 1)
|
||||
|
||||
graded = noDropGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.0916666666666666) # 100% + 10% / 12 assignments
|
||||
self.assertEqual(len(graded['section_breakdown']), 12 + 1)
|
||||
|
||||
graded = overflowGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.8880952380952382) # 100% + 10% / 5 assignments
|
||||
self.assertEqual(len(graded['section_breakdown']), 7 + 1)
|
||||
|
||||
graded = labGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.9226190476190477)
|
||||
self.assertEqual(len(graded['section_breakdown']), 7 + 1)
|
||||
|
||||
def test_WeightedSubsectionsGrader(self):
|
||||
#First, a few sub graders
|
||||
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
|
||||
labGrader = graders.AssignmentFormatGrader("Lab", 7, 3)
|
||||
midtermGrader = graders.SingleSectionGrader("Midterm", "Midterm Exam")
|
||||
|
||||
weightedGrader = graders.WeightedSubsectionsGrader([(homeworkGrader, homeworkGrader.category, 0.25),
|
||||
(labGrader, labGrader.category, 0.25),
|
||||
(midtermGrader, midtermGrader.category, 0.5)])
|
||||
|
||||
overOneWeightsGrader = graders.WeightedSubsectionsGrader([(homeworkGrader, homeworkGrader.category, 0.5),
|
||||
(labGrader, labGrader.category, 0.5),
|
||||
(midtermGrader, midtermGrader.category, 0.5)])
|
||||
|
||||
#The midterm should have all weight on this one
|
||||
zeroWeightsGrader = graders.WeightedSubsectionsGrader([(homeworkGrader, homeworkGrader.category, 0.0),
|
||||
(labGrader, labGrader.category, 0.0),
|
||||
(midtermGrader, midtermGrader.category, 0.5)])
|
||||
|
||||
#This should always have a final percent of zero
|
||||
allZeroWeightsGrader = graders.WeightedSubsectionsGrader([(homeworkGrader, homeworkGrader.category, 0.0),
|
||||
(labGrader, labGrader.category, 0.0),
|
||||
(midtermGrader, midtermGrader.category, 0.0)])
|
||||
|
||||
emptyGrader = graders.WeightedSubsectionsGrader([])
|
||||
|
||||
graded = weightedGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.5106547619047619)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = overOneWeightsGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.7688095238095238)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = zeroWeightsGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.2525)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = allZeroWeightsGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
for graded in [weightedGrader.grade(self.empty_gradesheet),
|
||||
weightedGrader.grade(self.incomplete_gradesheet),
|
||||
zeroWeightsGrader.grade(self.empty_gradesheet),
|
||||
allZeroWeightsGrader.grade(self.empty_gradesheet)]:
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = emptyGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
self.assertEqual(len(graded['section_breakdown']), 0)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 0)
|
||||
|
||||
def test_graderFromConf(self):
|
||||
|
||||
#Confs always produce a graders.WeightedSubsectionsGrader, so we test this by repeating the test
|
||||
#in test_graders.WeightedSubsectionsGrader, but generate the graders with confs.
|
||||
|
||||
weightedGrader = graders.grader_from_conf([
|
||||
{
|
||||
'type': "Homework",
|
||||
'min_count': 12,
|
||||
'drop_count': 2,
|
||||
'short_label': "HW",
|
||||
'weight': 0.25,
|
||||
},
|
||||
{
|
||||
'type': "Lab",
|
||||
'min_count': 7,
|
||||
'drop_count': 3,
|
||||
'category': "Labs",
|
||||
'weight': 0.25
|
||||
},
|
||||
{
|
||||
'type': "Midterm",
|
||||
'name': "Midterm Exam",
|
||||
'short_label': "Midterm",
|
||||
'weight': 0.5,
|
||||
},
|
||||
])
|
||||
|
||||
emptyGrader = graders.grader_from_conf([])
|
||||
|
||||
graded = weightedGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.5106547619047619)
|
||||
self.assertEqual(len(graded['section_breakdown']), (12 + 1) + (7 + 1) + 1)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 3)
|
||||
|
||||
graded = emptyGrader.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.0)
|
||||
self.assertEqual(len(graded['section_breakdown']), 0)
|
||||
self.assertEqual(len(graded['grade_breakdown']), 0)
|
||||
|
||||
#Test that graders can also be used instead of lists of dictionaries
|
||||
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
|
||||
homeworkGrader2 = graders.grader_from_conf(homeworkGrader)
|
||||
|
||||
graded = homeworkGrader2.grade(self.test_gradesheet)
|
||||
self.assertAlmostEqual(graded['percent'], 0.11)
|
||||
self.assertEqual(len(graded['section_breakdown']), 12 + 1)
|
||||
|
||||
#TODO: How do we test failure cases? The parser only logs an error when
|
||||
#it can't parse something. Maybe it should throw exceptions?
|
||||
|
||||
138
common/lib/xmodule/xmodule/tests/test_progress.py
Normal file
138
common/lib/xmodule/xmodule/tests/test_progress.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""Module progress tests"""
|
||||
|
||||
import unittest
|
||||
|
||||
from xmodule.progress import Progress
|
||||
from xmodule import x_module
|
||||
|
||||
from . import i4xs
|
||||
|
||||
class ProgressTest(unittest.TestCase):
|
||||
''' Test that basic Progress objects work. A Progress represents a
|
||||
fraction between 0 and 1.
|
||||
'''
|
||||
not_started = Progress(0, 17)
|
||||
part_done = Progress(2, 6)
|
||||
half_done = Progress(3, 6)
|
||||
also_half_done = Progress(1, 2)
|
||||
done = Progress(7, 7)
|
||||
|
||||
def test_create_object(self):
|
||||
# These should work:
|
||||
p = Progress(0, 2)
|
||||
p = Progress(1, 2)
|
||||
p = Progress(2, 2)
|
||||
|
||||
p = Progress(2.5, 5.0)
|
||||
p = Progress(3.7, 12.3333)
|
||||
|
||||
# These shouldn't
|
||||
self.assertRaises(ValueError, Progress, 0, 0)
|
||||
self.assertRaises(ValueError, Progress, 2, 0)
|
||||
self.assertRaises(ValueError, Progress, 1, -2)
|
||||
|
||||
self.assertRaises(TypeError, Progress, 0, "all")
|
||||
# check complex numbers just for the heck of it :)
|
||||
self.assertRaises(TypeError, Progress, 2j, 3)
|
||||
|
||||
def test_clamp(self):
|
||||
self.assertEqual((2, 2), Progress(3, 2).frac())
|
||||
self.assertEqual((0, 2), Progress(-2, 2).frac())
|
||||
|
||||
def test_frac(self):
|
||||
p = Progress(1, 2)
|
||||
(a, b) = p.frac()
|
||||
self.assertEqual(a, 1)
|
||||
self.assertEqual(b, 2)
|
||||
|
||||
def test_percent(self):
|
||||
self.assertEqual(self.not_started.percent(), 0)
|
||||
self.assertAlmostEqual(self.part_done.percent(), 33.33333333333333)
|
||||
self.assertEqual(self.half_done.percent(), 50)
|
||||
self.assertEqual(self.done.percent(), 100)
|
||||
|
||||
self.assertEqual(self.half_done.percent(), self.also_half_done.percent())
|
||||
|
||||
def test_started(self):
|
||||
self.assertFalse(self.not_started.started())
|
||||
|
||||
self.assertTrue(self.part_done.started())
|
||||
self.assertTrue(self.half_done.started())
|
||||
self.assertTrue(self.done.started())
|
||||
|
||||
def test_inprogress(self):
|
||||
# only true if working on it
|
||||
self.assertFalse(self.done.inprogress())
|
||||
self.assertFalse(self.not_started.inprogress())
|
||||
|
||||
self.assertTrue(self.part_done.inprogress())
|
||||
self.assertTrue(self.half_done.inprogress())
|
||||
|
||||
def test_done(self):
|
||||
self.assertTrue(self.done.done())
|
||||
self.assertFalse(self.half_done.done())
|
||||
self.assertFalse(self.not_started.done())
|
||||
|
||||
def test_str(self):
|
||||
self.assertEqual(str(self.not_started), "0/17")
|
||||
self.assertEqual(str(self.part_done), "2/6")
|
||||
self.assertEqual(str(self.done), "7/7")
|
||||
|
||||
def test_ternary_str(self):
|
||||
self.assertEqual(self.not_started.ternary_str(), "none")
|
||||
self.assertEqual(self.half_done.ternary_str(), "in_progress")
|
||||
self.assertEqual(self.done.ternary_str(), "done")
|
||||
|
||||
def test_to_js_status(self):
|
||||
'''Test the Progress.to_js_status_str() method'''
|
||||
|
||||
self.assertEqual(Progress.to_js_status_str(self.not_started), "none")
|
||||
self.assertEqual(Progress.to_js_status_str(self.half_done), "in_progress")
|
||||
self.assertEqual(Progress.to_js_status_str(self.done), "done")
|
||||
self.assertEqual(Progress.to_js_status_str(None), "NA")
|
||||
|
||||
def test_to_js_detail_str(self):
|
||||
'''Test the Progress.to_js_detail_str() method'''
|
||||
f = Progress.to_js_detail_str
|
||||
for p in (self.not_started, self.half_done, self.done):
|
||||
self.assertEqual(f(p), str(p))
|
||||
# But None should be encoded as NA
|
||||
self.assertEqual(f(None), "NA")
|
||||
|
||||
def test_add(self):
|
||||
'''Test the Progress.add_counts() method'''
|
||||
p = Progress(0, 2)
|
||||
p2 = Progress(1, 3)
|
||||
p3 = Progress(2, 5)
|
||||
pNone = None
|
||||
add = lambda a, b: Progress.add_counts(a, b).frac()
|
||||
|
||||
self.assertEqual(add(p, p), (0, 4))
|
||||
self.assertEqual(add(p, p2), (1, 5))
|
||||
self.assertEqual(add(p2, p3), (3, 8))
|
||||
|
||||
self.assertEqual(add(p2, pNone), p2.frac())
|
||||
self.assertEqual(add(pNone, p2), p2.frac())
|
||||
|
||||
def test_equality(self):
|
||||
'''Test that comparing Progress objects for equality
|
||||
works correctly.'''
|
||||
p = Progress(1, 2)
|
||||
p2 = Progress(2, 4)
|
||||
p3 = Progress(1, 2)
|
||||
self.assertTrue(p == p3)
|
||||
self.assertFalse(p == p2)
|
||||
|
||||
# Check != while we're at it
|
||||
self.assertTrue(p != p2)
|
||||
self.assertFalse(p != p3)
|
||||
|
||||
|
||||
class ModuleProgressTest(unittest.TestCase):
|
||||
''' Test that get_progress() does the right thing for the different modules
|
||||
'''
|
||||
def test_xmodule_default(self):
|
||||
'''Make sure default get_progress exists, returns None'''
|
||||
xm = x_module.XModule(i4xs, 'a://b/c/d/e', None, {})
|
||||
p = xm.get_progress()
|
||||
self.assertEqual(p, None)
|
||||
Reference in New Issue
Block a user