Skip to content

Commit e23ad1a

Browse files
committed
Add automated tests using Tox
This commit creates a test suite for the xblock and uses the Tox framework to make tests automatically run when the repository is ubdated. The suite of tests can be run as follows: python ./manage.py lms test --verbosity=1 xblock-submit-and-compare/submit_and_compare/tests.py --traceback --settings=test
1 parent fcd305e commit e23ad1a

File tree

9 files changed

+290
-2
lines changed

9 files changed

+290
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.coverage
2+
.tox/

.travis.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
sudo: false
2+
language: python
3+
cache: pip
4+
python:
5+
- '2.7'
6+
before_install:
7+
- 'uname -a'
8+
- 'python --version'
9+
install:
10+
- 'pip install tox'
11+
- 'virtualenv --version'
12+
- 'easy_install --version'
13+
- 'pip --version'
14+
- 'tox --version'
15+
script:
16+
- 'tox -v'
17+
branches:
18+
only:
19+
- 'master'
20+
env:
21+
- TOXENV=py27-dj14
22+
- TOXENV=py27-dj18
23+
- TOXENV=coveralls
24+
- TOXENV=pep8
25+
# TODO: Add pylint and pyflakes compliance
26+
# - TOXENV=pyflakes
27+
# - TOXENV=pylint

manage.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env python
2+
"""
3+
Manage the djangoapp
4+
"""
5+
import os
6+
import sys
7+
8+
from django.core.management import execute_from_command_line
9+
10+
if __name__ == '__main__':
11+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'submit_and_compare.settings')
12+
execute_from_command_line(sys.argv)

pylintrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[VARIABLES]
2+
dummy-variables-rgx=_|dummy
3+
4+
[REPORTS]
5+
reports=no
6+
7+
[MESSAGES CONTROL]
8+
disable=locally-disabled

setup.cfg

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[metadata]
2+
description-file = README.markdown
3+
4+
[nosetests]
5+
cover-package=submit_and_compare
6+
cover-tests=1

setup.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import os
44
from setuptools import setup
5-
5+
from setuptools.command.test import test as TestCommand
66

77
def package_data(pkg, root):
88
"""Generic function to find package_data for `pkg` under `root`."""
@@ -14,6 +14,25 @@ def package_data(pkg, root):
1414
return {pkg: data}
1515

1616

17+
class Tox(TestCommand):
18+
user_options = [('tox-args=', 'a', "Arguments to pass to tox")]
19+
def initialize_options(self):
20+
TestCommand.initialize_options(self)
21+
self.tox_args = None
22+
def finalize_options(self):
23+
TestCommand.finalize_options(self)
24+
self.test_args = []
25+
self.test_suite = True
26+
def run_tests(self):
27+
# import here, cause outside the eggs aren't loaded
28+
import tox
29+
import shlex
30+
args = self.tox_args
31+
if args:
32+
args = shlex.split(self.tox_args)
33+
errno = tox.cmdline(args=args)
34+
sys.exit(errno)
35+
1736
setup(
1837
name='xblock-submit-and-compare',
1938
version='0.5',
@@ -23,11 +42,21 @@ def package_data(pkg, root):
2342
],
2443
install_requires=[
2544
'XBlock',
45+
'django',
46+
'edx-opaque-keys',
47+
'mock',
48+
'django_nose',
49+
'coverage',
2650
],
2751
entry_points={
2852
'xblock.v1': [
2953
'submit-and-compare = submit_and_compare:SubmitAndCompareXBlock',
3054
]
3155
},
3256
package_data=package_data("submit_and_compare", "static"),
33-
)
57+
tests_require=[
58+
],
59+
cmdclass={
60+
'test': Tox,
61+
},
62+
)

submit_and_compare/settings.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
DATABASES = {
2+
'default': {
3+
'ENGINE': 'django.db.backends.sqlite3',
4+
# 'NAME': 'intentionally-omitted',
5+
},
6+
}
7+
8+
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
9+
10+
INSTALLED_APPS = (
11+
'django_nose',
12+
)
13+
14+
SECRET_KEY = 'TOX_SECRET_KEY'

submit_and_compare/tests.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""
2+
Module Placeholder Docstring
3+
"""
4+
import unittest
5+
6+
import cgi
7+
import mock
8+
from django.test.client import Client
9+
from django.utils.translation import ugettext as _
10+
from opaque_keys.edx.locations import SlashSeparatedCourseKey
11+
from xblock.field_data import DictFieldData
12+
13+
from .submit_and_compare import SubmitAndCompareXBlock
14+
15+
16+
class SubmitAndCompareXblockTestCase(unittest.TestCase):
17+
# pylint: disable=too-many-instance-attributes, too-many-public-methods
18+
"""
19+
A complete suite of unit tests for the Free-text Response XBlock
20+
"""
21+
@classmethod
22+
def make_an_xblock(cls, **kw):
23+
"""
24+
Helper method that creates a Free-text Response XBlock
25+
"""
26+
course_id = SlashSeparatedCourseKey('foo', 'bar', 'baz')
27+
runtime = mock.Mock(course_id=course_id)
28+
scope_ids = mock.Mock()
29+
field_data = DictFieldData(kw)
30+
xblock = SubmitAndCompareXBlock(runtime, field_data, scope_ids)
31+
xblock.xmodule_runtime = runtime
32+
return xblock
33+
34+
def setUp(self):
35+
# pylint: disable=super-method-not-called
36+
self.xblock = SubmitAndCompareXblockTestCase.make_an_xblock()
37+
self.client = Client()
38+
39+
def test_student_view(self):
40+
# pylint: disable=protected-access
41+
"""
42+
Checks the student view for student specific instance variables.
43+
"""
44+
student_view_html = self.student_view_html()
45+
self.assertIn(self.xblock.display_name, student_view_html)
46+
self.assertIn(
47+
self.xblock._get_body(self.xblock.question_string),
48+
student_view_html,
49+
)
50+
self.assertIn(self.xblock._get_problem_progress(), student_view_html)
51+
52+
def test_studio_view(self):
53+
"""
54+
Checks studio view for instance variables specified by the instructor.
55+
"""
56+
studio_view_html = self.studio_view_html()
57+
self.assertIn(self.xblock.display_name, studio_view_html)
58+
self.assertIn(
59+
cgi.escape(self.xblock._get_body(self.xblock.question_string)),
60+
studio_view_html,
61+
)
62+
self.assertIn(str(self.xblock.max_attempts), studio_view_html)
63+
64+
def test_initialization_variables(self):
65+
"""
66+
Checks that all instance variables are initialized correctly
67+
"""
68+
self.assertEquals('Submit and Compare', self.xblock.display_name)
69+
self.assertIn(
70+
'Before you begin the simulation',
71+
self.xblock.question_string,
72+
)
73+
self.assertEquals(0.0, self.xblock.score)
74+
self.assertEquals(0, self.xblock.max_attempts)
75+
self.assertEquals('', self.xblock.student_answer)
76+
self.assertEquals(0, self.xblock.count_attempts)
77+
78+
def student_view_html(self):
79+
"""
80+
Helper method that returns the html of student_view
81+
"""
82+
return self.xblock.student_view().content
83+
84+
def studio_view_html(self):
85+
"""
86+
Helper method that returns the html of studio_view
87+
"""
88+
return self.xblock.studio_view(context=None).content
89+
90+
def test_problem_progress_score_zero_weight_singular(self):
91+
# pylint: disable=invalid-name, protected-access
92+
"""
93+
Tests that the the string returned by get_problem_progress
94+
when the weight of the problem is singular, and the score is zero
95+
"""
96+
self.xblock.score = 0
97+
self.xblock.weight = 1
98+
self.assertEquals(
99+
_('1 point possible'),
100+
self.xblock._get_problem_progress(),
101+
)
102+
103+
def test_problem_progress_score_zero_weight_plural(self):
104+
# pylint: disable=invalid-name, protected-access
105+
"""
106+
Tests that the the string returned by get_problem_progress
107+
when the weight of the problem is plural, and the score is zero
108+
"""
109+
self.xblock.score = 0
110+
self.xblock.weight = 3
111+
self.assertEquals(
112+
_('3 points possible'),
113+
self.xblock._get_problem_progress(),
114+
)
115+
116+
def test_problem_progress_score_positive_weight_singular(self):
117+
# pylint: disable=invalid-name, protected-access
118+
"""
119+
Tests that the the string returned by get_problem_progress
120+
when the weight of the problem is singular, and the score is positive
121+
"""
122+
self.xblock.score = 1
123+
self.xblock.weight = 1
124+
self.assertEquals(
125+
_('1/1 point'),
126+
self.xblock._get_problem_progress(),
127+
)
128+
129+
def test_problem_progress_score_positive_weight_plural(self):
130+
# pylint: disable=invalid-name, protected-access
131+
"""
132+
Tests that the the string returned by get_problem_progress
133+
when the weight of the problem is plural, and the score is positive
134+
"""
135+
self.xblock.score = 1.5
136+
self.xblock.weight = 3
137+
self.assertEquals(
138+
_('1.5/3 points'),
139+
self.xblock._get_problem_progress(),
140+
)

tox.ini

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[tox]
2+
downloadcache = {toxworkdir}/_download/
3+
envlist = py27-dj{14,18},coverage,pep8,pylint,pyflakes
4+
5+
[testenv]
6+
commands = {envpython} manage.py test
7+
8+
[testenv:py27-dj14]
9+
deps =
10+
django == 1.4.22
11+
12+
[testenv:py27-dj18]
13+
deps =
14+
django >= 1.8, < 1.9
15+
16+
[testenv:pep8]
17+
deps = pep8
18+
commands = {envbindir}/pep8 submit_and_compare/
19+
20+
[testenv:pylint]
21+
deps = pylint
22+
commands = {envbindir}/pylint submit_and_compare/
23+
24+
[testenv:pyflakes]
25+
deps = pyflakes
26+
commands = {envbindir}/pyflakes submit_and_compare/
27+
28+
# Note: Coverage appears lower than it actually is.
29+
# For a detailed explanation of why this is the case, see:
30+
# https://docs.python.org/devguide/coverage.html#common-gotchas
31+
[testenv:coverage]
32+
deps =
33+
coverage
34+
setenv =
35+
NOSE_COVER_TESTS=1
36+
NOSE_WITH_COVERAGE=1
37+
commands =
38+
{envpython} manage.py test
39+
40+
[testenv:coveralls]
41+
deps =
42+
coverage
43+
coveralls
44+
setenv =
45+
NOSE_COVER_TESTS=1
46+
NOSE_WITH_COVERAGE=1
47+
passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
48+
commands =
49+
{envpython} manage.py test
50+
{envbindir}/coveralls

0 commit comments

Comments
 (0)