Commit 2a68f9b1 by Dillon Dumesnil

Improved tests(exclamation point)

parent 70fff66e
"""
Test scenarios for the review xblock.
"""
import ddt
import unittest
from django.conf import settings
......@@ -10,15 +11,14 @@ from nose.plugins.attrib import attr
from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory
from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from review import get_review_ids
import crum
class TestReviewXBlock(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
class TestReviewXBlock(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
Create the test environment with the review xblock.
"""
......@@ -27,18 +27,16 @@ class TestReviewXBlock(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
]
XBLOCK_NAMES = ['review']
@classmethod
def setUpClass(cls):
def setUp(self):
super(TestReviewXBlock, self).setUp()
# Nose runs setUpClass methods even if a class decorator says to skip
# the class: https://github.com/nose-devs/nose/issues/946
# So, skip the test class here if we are not in the LMS.
if settings.ROOT_URLCONF != 'lms.urls':
raise unittest.SkipTest('Test only valid in lms')
super(TestReviewXBlock, cls).setUpClass()
# Set up for the actual course
cls.course_actual = CourseFactory.create(
self.course_actual = CourseFactory.create(
display_name='Review_Test_Course_ACTUAL',
org='DillonX',
number='DAD101x',
......@@ -46,147 +44,139 @@ class TestReviewXBlock(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
)
# There are multiple sections so the learner can load different
# problems, but should only be shown review problems from what they have loaded
with cls.store.bulk_operations(cls.course_actual.id, emit_signals=False):
cls.chapter_actual = ItemFactory.create(
parent=cls.course_actual, display_name='Overview'
with self.store.bulk_operations(self.course_actual.id, emit_signals=False):
self.chapter_actual = ItemFactory.create(
parent=self.course_actual, display_name='Overview'
)
cls.section1_actual = ItemFactory.create(
parent=cls.chapter_actual, display_name='Section 1'
self.section1_actual = ItemFactory.create(
parent=self.chapter_actual, display_name='Section 1'
)
cls.unit1_actual = ItemFactory.create(
parent=cls.section1_actual, display_name='New Unit 1'
self.unit1_actual = ItemFactory.create(
parent=self.section1_actual, display_name='New Unit 1'
)
cls.xblock1_actual = ItemFactory.create(
parent=cls.unit1_actual,
self.xblock1_actual = ItemFactory.create(
parent=self.unit1_actual,
category='problem',
display_name='Problem 1'
)
cls.xblock2_actual = ItemFactory.create(
parent=cls.unit1_actual,
self.xblock2_actual = ItemFactory.create(
parent=self.unit1_actual,
category='problem',
display_name='Problem 2'
)
cls.xblock3_actual = ItemFactory.create(
parent=cls.unit1_actual,
self.xblock3_actual = ItemFactory.create(
parent=self.unit1_actual,
category='problem',
display_name='Problem 3'
)
cls.xblock4_actual = ItemFactory.create(
parent=cls.unit1_actual,
self.xblock4_actual = ItemFactory.create(
parent=self.unit1_actual,
category='problem',
display_name='Problem 4'
)
cls.section2_actual = ItemFactory.create(
parent=cls.chapter_actual, display_name='Section 2'
self.section2_actual = ItemFactory.create(
parent=self.chapter_actual, display_name='Section 2'
)
cls.unit2_actual = ItemFactory.create(
parent=cls.section2_actual, display_name='New Unit 2'
self.unit2_actual = ItemFactory.create(
parent=self.section2_actual, display_name='New Unit 2'
)
cls.xblock5_actual = ItemFactory.create(
parent=cls.unit2_actual,
self.xblock5_actual = ItemFactory.create(
parent=self.unit2_actual,
category='problem',
display_name='Problem 5'
)
cls.section3_actual = ItemFactory.create(
parent=cls.chapter_actual, display_name='Section 3'
self.section3_actual = ItemFactory.create(
parent=self.chapter_actual, display_name='Section 3'
)
cls.unit3_actual = ItemFactory.create(
parent=cls.section3_actual, display_name='New Unit 3'
self.unit3_actual = ItemFactory.create(
parent=self.section3_actual, display_name='New Unit 3'
)
cls.xblock6_actual = ItemFactory.create(
parent=cls.unit3_actual,
self.xblock6_actual = ItemFactory.create(
parent=self.unit3_actual,
category='problem',
display_name='Problem 6'
)
# This is the actual review xBlock
# When implemented, the review is in its own section as a
# stand-alone unit.
cls.review_section_actual = ItemFactory.create(
parent=cls.chapter_actual, display_name='Review Subsection'
self.review_section_actual = ItemFactory.create(
parent=self.chapter_actual, display_name='Review Subsection'
)
cls.review_unit_actual = ItemFactory.create(
parent=cls.review_section_actual, display_name='Review Unit'
)
cls.review_xblock_actual = ItemFactory.create(
parent=cls.review_unit_actual,
category='review',
display_name='Review Tool'
self.review_unit_actual = ItemFactory.create(
parent=self.review_section_actual, display_name='Review Unit'
)
cls.course_actual_url = reverse(
self.course_actual_url = reverse(
'courseware_section',
kwargs={
'course_id': unicode(cls.course_actual.id),
'course_id': unicode(self.course_actual.id),
'chapter': 'Overview',
'section': 'Welcome',
}
)
# Set up for the review course where the review problems are hosted
cls.course_review = CourseFactory.create(
self.course_review = CourseFactory.create(
display_name='Review_Test_Course_REVIEW',
org='DillonX',
number='DAD101rx',
number='DAD101x_review',
run='3T2017'
)
with cls.store.bulk_operations(cls.course_review.id, emit_signals=True):
cls.chapter_review = ItemFactory.create(
parent=cls.course_review, display_name='Overview'
with self.store.bulk_operations(self.course_review.id, emit_signals=True):
self.chapter_review = ItemFactory.create(
parent=self.course_review, display_name='Overview'
)
cls.section_review = ItemFactory.create(
parent=cls.chapter_review, display_name='Welcome'
self.section_review = ItemFactory.create(
parent=self.chapter_review, display_name='Welcome'
)
cls.unit1_review = ItemFactory.create(
parent=cls.section_review, display_name='New Unit 1'
self.unit1_review = ItemFactory.create(
parent=self.section_review, display_name='New Unit 1'
)
cls.xblock1_review = ItemFactory.create(
parent=cls.unit1_review,
self.xblock1_review = ItemFactory.create(
parent=self.unit1_review,
category='problem',
display_name='Problem 1'
)
cls.xblock2_review = ItemFactory.create(
parent=cls.unit1_review,
self.xblock2_review = ItemFactory.create(
parent=self.unit1_review,
category='problem',
display_name='Problem 2'
)
cls.xblock3_review = ItemFactory.create(
parent=cls.unit1_review,
self.xblock3_review = ItemFactory.create(
parent=self.unit1_review,
category='problem',
display_name='Problem 3'
)
cls.xblock4_review = ItemFactory.create(
parent=cls.unit1_review,
self.xblock4_review = ItemFactory.create(
parent=self.unit1_review,
category='problem',
display_name='Problem 4'
)
cls.unit2_review = ItemFactory.create(
parent=cls.section_review, display_name='New Unit 2'
self.unit2_review = ItemFactory.create(
parent=self.section_review, display_name='New Unit 2'
)
cls.xblock5_review = ItemFactory.create(
parent=cls.unit2_review,
self.xblock5_review = ItemFactory.create(
parent=self.unit2_review,
category='problem',
display_name='Problem 5'
)
cls.unit3_review = ItemFactory.create(
parent=cls.section_review, display_name='New Unit 3'
self.unit3_review = ItemFactory.create(
parent=self.section_review, display_name='New Unit 3'
)
cls.xblock6_review = ItemFactory.create(
parent=cls.unit3_review,
self.xblock6_review = ItemFactory.create(
parent=self.unit3_review,
category='problem',
display_name='Problem 6'
)
cls.course_review_url = reverse(
self.course_review_url = reverse(
'courseware_section',
kwargs={
'course_id': unicode(cls.course_review.id),
'course_id': unicode(self.course_review.id),
'chapter': 'Overview',
'section': 'Welcome',
}
)
def setUp(self):
super(TestReviewXBlock, self).setUp()
for idx, student in enumerate(self.STUDENTS):
username = 'u{}'.format(idx)
self.create_account(username, student['email'], student['password'])
......@@ -203,6 +193,7 @@ class TestReviewXBlock(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
@attr(shard=1)
@ddt.ddt
class TestReviewFunctions(TestReviewXBlock):
"""
Check that the essential functions of the Review xBlock work as expected.
......@@ -218,6 +209,12 @@ class TestReviewFunctions(TestReviewXBlock):
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_actual)
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_review)
self.review_xblock_actual = ItemFactory.create(
parent=self.review_unit_actual,
category='review',
display_name='Review Tool',
)
# Loading the review section
response = self.client.get(reverse(
'courseware_section',
......@@ -229,33 +226,53 @@ class TestReviewFunctions(TestReviewXBlock):
))
expected_h2 = 'Nothing to review'
expected_p = 'Oh no! You have not interacted with enough problems yet to have any to review. '\
'Go back and try more problems so you have content to review.'
self.assertIn(expected_h2, response.content)
self.assertIn(expected_p, response.content)
def test_too_few_review_problems(self):
@ddt.data(2, 5, 6, 7)
def test_too_few_review_problems(self, num_desired):
"""
If a user does not have enough problems to review, they should
receive a response to go out and try more problems so they have
material to review.
TODO: This test is hardcoded to assume the number of review
problems to show is > 1 (which it should be). Ideally this could
be dependent on the number of desired review problems
"""
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_actual)
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_review)
# Loading 1 problem so the learner has something in the CSM
self.client.get(reverse(
'courseware_section',
kwargs={
'course_id': self.course_actual.id,
'chapter': self.chapter_actual.location.name,
'section': self.section2_actual.location.name,
}
))
# Want to load fewer problems than num_desired
if num_desired > 4:
self.client.get(reverse(
'courseware_section',
kwargs={
'course_id': self.course_actual.id,
'chapter': self.chapter_actual.location.name,
'section': self.section1_actual.location.name,
}
))
if num_desired > 5:
self.client.get(reverse(
'courseware_section',
kwargs={
'course_id': self.course_actual.id,
'chapter': self.chapter_actual.location.name,
'section': self.section2_actual.location.name,
}
))
if num_desired > 6:
self.client.get(reverse(
'courseware_section',
kwargs={
'course_id': self.course_actual.id,
'chapter': self.chapter_actual.location.name,
'section': self.section3_actual.location.name,
}
))
self.review_xblock_actual = ItemFactory.create(
parent=self.review_unit_actual,
category='review',
display_name='Review Tool',
num_desired=num_desired
)
# Loading the review section
response = self.client.get(reverse(
......@@ -268,25 +285,19 @@ class TestReviewFunctions(TestReviewXBlock):
))
expected_h2 = 'Nothing to review'
expected_p = 'Oh no! You have not interacted with enough problems yet to have any to review. '\
'Go back and try more problems so you have content to review.'
self.assertIn(expected_h2, response.content)
self.assertIn(expected_p, response.content)
def test_review_problems(self):
@ddt.data(2, 3, 4, 5, 6)
def test_review_problems(self, num_desired):
"""
If a user has enough problems to review, they should
receive a response where there are review problems for them to try.
TODO: This test is hardcoded to assume the number of review
problems to show is <= 5. Ideally this should
be dependent on the number of desired review problems
"""
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_actual)
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_review)
# Loading 5 problems so the learner has enough problems in the CSM
# Loading problems so the learner has enough problems in the CSM
self.client.get(reverse(
'courseware_section',
kwargs={
......@@ -303,7 +314,21 @@ class TestReviewFunctions(TestReviewXBlock):
'section': self.section2_actual.location.name,
}
))
self.client.get(reverse(
'courseware_section',
kwargs={
'course_id': self.course_actual.id,
'chapter': self.chapter_actual.location.name,
'section': self.section3_actual.location.name,
}
))
self.review_xblock_actual = ItemFactory.create(
parent=self.review_unit_actual,
category='review',
display_name='Review Tool',
num_desired=num_desired
)
# Loading the review section
response = self.client.get(reverse(
'courseware_section',
......@@ -314,37 +339,36 @@ class TestReviewFunctions(TestReviewXBlock):
}
))
expected_header_text = 'Below are 5 review problems for you to try out and see '\
'how well you have mastered the material of this class'
# The problems are defaulted to correct upon load, so the
# correctness text should be displayed as follows
expected_header_text = 'Review Component'
# The problems are defaulted to correct upon load
# This happens because the problems "raw_possible" field is 0 and the
# "raw_earned" field is also 0.
expected_correctness_text = 'When you originally tried this problem, you ended '\
'up being correct after 0 attempts.'
expected_correctness_text = 'correct'
expected_problems = ['Review Problem 1', 'Review Problem 2', 'Review Problem 3',
'Review Problem 4', 'Review Problem 5']
expected_url_beginning = 'https://courses.edx.org/xblock/block-v1:DillonX/DAD101rx/3T2017+type@problem+block@'
'Review Problem 4', 'Review Problem 5', 'Review Problem 6']
expected_url_beginning = 'https://courses.edx.org/xblock/block-v1:DillonX/DAD101x_review/3T2017+type@problem+block@'
self.assertIn(expected_header_text, response.content)
self.assertEqual(response.content.count(expected_correctness_text), 5)
self.assertEqual(response.content.count(expected_correctness_text), num_desired)
# Since the problems are randomly selected, we have to check
# the correct number of problems are returned.
count = 0
for problem in expected_problems:
self.assertIn(problem, response.content)
self.assertEqual(response.content.count(expected_url_beginning), 5)
if problem in response.content:
count += 1
self.assertEqual(count, num_desired)
self.assertEqual(response.content.count(expected_url_beginning), num_desired)
def test_review_problem_urls(self):
@ddt.data(1, 2, 3, 4, 5, 6)
def test_review_problem_urls(self, num_desired):
"""
Verify that the URLs returned from the Review xBlock are valid and
correct URLs for the problems the learner has seen.
TODO: This test is hardcoded to assume the number of review
problems to show is 5. Ideally this should
be dependent on the number of desired review problems
"""
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_actual)
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_review)
# Loading 5 problems so the learner has enough problems in the CSM
# Loading problems so the learner has enough problems in the CSM
self.client.get(reverse(
'courseware_section',
kwargs={
......@@ -361,38 +385,49 @@ class TestReviewFunctions(TestReviewXBlock):
'section': self.section2_actual.location.name,
}
))
self.client.get(reverse(
'courseware_section',
kwargs={
'course_id': self.course_actual.id,
'chapter': self.chapter_actual.location.name,
'section': self.section3_actual.location.name,
}
))
user = User.objects.get(email=self.STUDENTS[0]['email'])
crum.set_current_user(user)
result_urls = get_review_ids.get_problems(5, self.course_actual.id)
result_urls = get_review_ids.get_problems(num_desired, self.course_actual.id)
url_beginning = 'https://courses.edx.org/xblock/block-v1:DillonX/DAD101rx/3T2017+type@problem+block@'
url_beginning = 'https://courses.edx.org/xblock/block-v1:DillonX/DAD101x_review/3T2017+type@problem+block@'
expected_urls = [
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_1', 'correct', 0),
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_2', 'correct', 0),
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_3', 'correct', 0),
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_4', 'correct', 0),
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_5', 'correct', 0)
(url_beginning + 'Problem_1', True, 0),
(url_beginning + 'Problem_2', True, 0),
(url_beginning + 'Problem_3', True, 0),
(url_beginning + 'Problem_4', True, 0),
(url_beginning + 'Problem_5', True, 0),
(url_beginning + 'Problem_6', True, 0)
]
# Since the problems are randomly selected, we have to check
# the correct number of urls are returned.
count = 0
for url in expected_urls:
self.assertIn(url, result_urls)
if url in result_urls:
count += 1
self.assertEqual(count, num_desired)
def test_review_problem_urls_unique_problem(self):
@ddt.data(1, 2, 3, 4, 5)
def test_review_problem_urls_unique_problem(self, num_desired):
"""
Verify that the URLs returned from the Review xBlock are valid and
correct URLs for the problems the learner has seen. This test will give
a unique problem to a learner and verify only that learner sees
it as a review
TODO: This test is hardcoded to assume the number of review
problems to show is 5. Ideally this should
be dependent on the number of desired review problems
"""
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_actual)
self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password'], self.course_review)
# Loading 5 problems so the learner has enough problems in the CSM
# Loading problems so the learner has enough problems in the CSM
self.client.get(reverse(
'courseware_section',
kwargs={
......@@ -412,21 +447,26 @@ class TestReviewFunctions(TestReviewXBlock):
user = User.objects.get(email=self.STUDENTS[0]['email'])
crum.set_current_user(user)
result_urls = get_review_ids.get_problems(5, self.course_actual.id)
result_urls = get_review_ids.get_problems(num_desired, self.course_actual.id)
url_beginning = 'https://courses.edx.org/xblock/block-v1:DillonX/DAD101rx/3T2017+type@problem+block@'
url_beginning = 'https://courses.edx.org/xblock/block-v1:DillonX/DAD101x_review/3T2017+type@problem+block@'
expected_urls = [
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_1', 'correct', 0),
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_2', 'correct', 0),
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_3', 'correct', 0),
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_4', 'correct', 0),
(url_beginning + 'Problem_1', True, 0),
(url_beginning + 'Problem_2', True, 0),
(url_beginning + 'Problem_3', True, 0),
(url_beginning + 'Problem_4', True, 0),
# This is the unique problem
(url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_6', 'correct', 0)
(url_beginning + 'Problem_6', True, 0)
]
expected_not_loaded_problem = (url_beginning + 'i4x://DillonX/DAD101x/problem/Problem_5', 'correct', 0)
expected_not_loaded_problem = (url_beginning + 'Problem_5', True, 0)
# Since the problems are randomly selected, we have to check
# the correct number of urls are returned.
count = 0
for url in expected_urls:
self.assertIn(url, result_urls)
if url in result_urls:
count += 1
self.assertEqual(count, num_desired)
self.assertNotIn(expected_not_loaded_problem, result_urls)
"""
......@@ -460,7 +500,7 @@ class TestReviewFunctions(TestReviewXBlock):
crum.set_current_user(user)
result_url = get_review_ids.get_vertical(self.course_actual.id)
expected_url = 'https://courses.edx.org/xblock/block-v1:DillonX/DAD101rx/3T2017+type@'\
'vertical+block@i4x://DillonX/DAD101x/chapter/New_Unit_1'
expected_url = 'https://courses.edx.org/xblock/block-v1:DillonX/DAD101x_review/3T2017+type@'\
'vertical+block@New_Unit_1'
self.assertEqual(result_url, expected_url)
......@@ -108,4 +108,5 @@ git+https://github.com/mitodl/edx-sga.git@d019b8a050c056db535e3ff13c93096145a932
git+https://github.com/open-craft/xblock-poll@7ba819b968fe8faddb78bb22e1fe7637005eb414#egg=xblock-poll==1.2.7
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.0.18#egg=xblock-drag-and-drop-v2==2.0.18
# TODO: Deploy xblock-review to PyPI and pin it before going to master. Talk to Feanil if any questions
-e git+https://github.com/Dillon-Dumesnil/xblock-review@master#egg=xblock-review
# For the purpose of being able to build during this PR time, I'm setting the branch to PR. Will switch to PyPI later
-e git+https://github.com/Dillon-Dumesnil/xblock-review@PR#egg=xblock-review
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment