Commit 5e6dedcd by Clinton Blackburn

Test Cleanup

Extracted mixins to make tests a bit more extensible. Removed some duplicated code.
parent 0674b790
...@@ -20,7 +20,7 @@ clean: ...@@ -20,7 +20,7 @@ clean:
coverage erase coverage erase
test: clean test: clean
. ./.test_env && ./manage.py test --settings=analyticsdataserver.settings.test \ . ./.test_env && ./manage.py test --settings=analyticsdataserver.settings.test --with-ignore-docstrings \
--exclude-dir=analyticsdataserver/settings --with-coverage --cover-inclusive --cover-branches \ --exclude-dir=analyticsdataserver/settings --with-coverage --cover-inclusive --cover-branches \
--cover-html --cover-html-dir=$(COVERAGE)/html/ \ --cover-html --cover-html-dir=$(COVERAGE)/html/ \
--cover-xml --cover-xml-file=$(COVERAGE)/coverage.xml \ --cover-xml --cover-xml-file=$(COVERAGE)/coverage.xml \
......
...@@ -16,6 +16,112 @@ from analytics_data_api.v0.tests.utils import flatten ...@@ -16,6 +16,112 @@ from analytics_data_api.v0.tests.utils import flatten
from analyticsdataserver.tests import TestCaseWithAuthentication from analyticsdataserver.tests import TestCaseWithAuthentication
# pylint: disable=no-member
class CourseViewTestCaseMixin(object):
model = None
api_root_path = '/api/v0/'
path = None
order_by = []
def format_as_response(self, *args):
"""
Format given data as a response that would be issued by the endpoint.
Arguments
args -- Iterable list of objects
"""
raise NotImplementedError
def get_latest_data(self):
"""
Return the latest row/rows that would be returned if a user made a call
to the endpoint with no date filtering.
Return value must be an iterable.
"""
raise NotImplementedError
def test_get_not_found(self):
""" Requests made against non-existent courses should return a 404 """
course_id = 'edX/DemoX/Non_Existent_Course'
response = self.authenticated_get('%scourses/%s%s' % (self.api_root_path, course_id, self.path))
self.assertEquals(response.status_code, 404)
def test_get(self):
""" Verify the endpoint returns an HTTP 200 status and the correct data. """
# Validate the basic response status
response = self.authenticated_get('%scourses/%s%s' % (self.api_root_path, self.course_id, self.path))
self.assertEquals(response.status_code, 200)
# Validate the data is correct and sorted chronologically
expected = self.format_as_response(*self.get_latest_data())
self.assertEquals(response.data, expected)
def test_get_csv(self):
""" Verify the endpoint returns data that has been properly converted to CSV. """
path = '%scourses/%s%s' % (self.api_root_path, self.course_id, self.path)
csv_content_type = 'text/csv'
response = self.authenticated_get(path, HTTP_ACCEPT=csv_content_type)
# Validate the basic response status and content code
self.assertEquals(response.status_code, 200)
self.assertEquals(response['Content-Type'].split(';')[0], csv_content_type)
# Validate the actual data
data = self.format_as_response(*self.get_latest_data())
data = map(flatten, data)
# The CSV renderer sorts the headers alphabetically
fieldnames = sorted(data[0].keys())
# Generate the expected CSV output
expected = StringIO.StringIO()
writer = csv.DictWriter(expected, fieldnames)
writer.writeheader()
writer.writerows(data)
self.assertEqual(response.content, expected.getvalue())
def test_get_with_intervals(self):
""" Verify the endpoint returns multiple data points when supplied with an interval of dates. """
raise NotImplementedError
def assertIntervalFilteringWorks(self, expected_response, start_date, end_date):
# If start date is after date of existing data, no data should be returned
date = (start_date + datetime.timedelta(days=30)).strftime(settings.DATE_FORMAT)
response = self.authenticated_get('%scourses/%s%s?start_date=%s' % (self.api_root_path, self.course_id, self.path, date))
self.assertEquals(response.status_code, 200)
self.assertListEqual([], response.data)
# If end date is before date of existing data, no data should be returned
date = (start_date - datetime.timedelta(days=30)).strftime(settings.DATE_FORMAT)
response = self.authenticated_get('%scourses/%s%s?end_date=%s' % (self.api_root_path, self.course_id, self.path, date))
self.assertEquals(response.status_code, 200)
self.assertListEqual([], response.data)
# If data falls in date range, data should be returned
start_date = start_date.strftime(settings.DATE_FORMAT)
end_date = end_date.strftime(settings.DATE_FORMAT)
response = self.authenticated_get(
'%scourses/%s%s?start_date=%s&end_date=%s' % (self.api_root_path, self.course_id, self.path, start_date, end_date))
self.assertEquals(response.status_code, 200)
self.assertListEqual(response.data, expected_response)
class CourseEnrollmentViewTestCaseMixin(CourseViewTestCaseMixin):
def setUp(self):
super(CourseEnrollmentViewTestCaseMixin, self).setUp()
self.course_id = 'edX/DemoX/Demo_Course'
self.date = datetime.date(2014, 1, 1)
def get_latest_data(self):
return self.model.objects.filter(date=self.date).order_by('date', *self.order_by)
def test_get_with_intervals(self):
expected = self.format_as_response(*self.model.objects.filter(date=self.date))
self.assertIntervalFilteringWorks(expected, self.date, self.date + datetime.timedelta(days=1))
class CourseActivityLastWeekTest(TestCaseWithAuthentication): class CourseActivityLastWeekTest(TestCaseWithAuthentication):
# pylint: disable=line-too-long # pylint: disable=line-too-long
def setUp(self): def setUp(self):
...@@ -98,96 +204,19 @@ class CourseActivityLastWeekTest(TestCaseWithAuthentication): ...@@ -98,96 +204,19 @@ class CourseActivityLastWeekTest(TestCaseWithAuthentication):
self.assertEquals(response.data, self.get_activity_record(activity_type=activity_type, count=400)) self.assertEquals(response.data, self.get_activity_record(activity_type=activity_type, count=400))
# pylint: disable=no-member class CourseEnrollmentByBirthYearViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithAuthentication):
class CourseEnrollmentViewTestCase(object):
model = None
path = None
order_by = []
def get_expected_response(self, *args):
raise NotImplementedError
def test_get_not_found(self):
""" Requests made against non-existent courses should return a 404 """
course_id = 'edX/DemoX/Non_Existent_Course'
response = self.authenticated_get('/api/v0/courses/%s%s' % (course_id, self.path))
self.assertEquals(response.status_code, 404)
def test_get(self):
# Validate the basic response status
response = self.authenticated_get('/api/v0/courses/%s%s' % (self.course_id, self.path,))
self.assertEquals(response.status_code, 200)
# Validate the data is correct and sorted chronologically
expected = self.get_expected_response(*self.model.objects.filter(date=self.date).order_by('date',
*self.order_by)) # pylint: disable=line-too-long
self.assertEquals(response.data, expected)
def test_get_csv(self):
path = '/api/v0/courses/%s%s' % (self.course_id, self.path,)
csv_content_type = 'text/csv'
response = self.authenticated_get(path, HTTP_ACCEPT=csv_content_type)
# Validate the basic response status and content code
self.assertEquals(response.status_code, 200)
self.assertEquals(response['Content-Type'].split(';')[0], csv_content_type)
# Validate the actual data
data = self.get_expected_response(*self.model.objects.filter(date=self.date))
data = map(flatten, data)
# The CSV renderer sorts the headers alphabetically
fieldnames = sorted(data[0].keys())
# Generate the expected CSV output
expected = StringIO.StringIO()
writer = csv.DictWriter(expected, fieldnames)
writer.writeheader()
writer.writerows(data)
self.assertEqual(response.content, expected.getvalue())
def test_get_with_intervals(self):
expected = self.get_expected_response(*self.model.objects.filter(date=self.date))
self.assertIntervalFilteringWorks(expected, self.date, self.date + datetime.timedelta(days=1))
def assertIntervalFilteringWorks(self, expected_response, start_date, end_date):
# If start date is after date of existing data, no data should be returned
date = (start_date + datetime.timedelta(days=30)).strftime(settings.DATE_FORMAT)
response = self.authenticated_get('/api/v0/courses/%s%s?start_date=%s' % (self.course_id, self.path, date))
self.assertEquals(response.status_code, 200)
self.assertListEqual([], response.data)
# If end date is before date of existing data, no data should be returned
date = (start_date - datetime.timedelta(days=30)).strftime(settings.DATE_FORMAT)
response = self.authenticated_get('/api/v0/courses/%s%s?end_date=%s' % (self.course_id, self.path, date))
self.assertEquals(response.status_code, 200)
self.assertListEqual([], response.data)
# If data falls in date range, data should be returned
start_date = start_date.strftime(settings.DATE_FORMAT)
end_date = end_date.strftime(settings.DATE_FORMAT)
response = self.authenticated_get(
'/api/v0/courses/%s%s?start_date=%s&end_date=%s' % (self.course_id, self.path, start_date, end_date))
self.assertEquals(response.status_code, 200)
self.assertListEqual(response.data, expected_response)
class CourseEnrollmentByBirthYearViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase):
path = '/enrollment/birth_year' path = '/enrollment/birth_year'
model = models.CourseEnrollmentByBirthYear model = models.CourseEnrollmentByBirthYear
order_by = ['birth_year'] order_by = ['birth_year']
@classmethod def setUp(self):
def setUpClass(cls): super(CourseEnrollmentByBirthYearViewTests, self).setUp()
cls.course_id = 'edX/DemoX/Demo_Course' G(self.model, course_id=self.course_id, date=self.date, birth_year=1956)
cls.date = datetime.date(2014, 1, 1) G(self.model, course_id=self.course_id, date=self.date, birth_year=1986)
G(cls.model, course_id=cls.course_id, date=cls.date, birth_year=1956) G(self.model, course_id=self.course_id, date=self.date - datetime.timedelta(days=10), birth_year=1956)
G(cls.model, course_id=cls.course_id, date=cls.date, birth_year=1986) G(self.model, course_id=self.course_id, date=self.date - datetime.timedelta(days=10), birth_year=1986)
G(cls.model, course_id=cls.course_id, date=cls.date - datetime.timedelta(days=10), birth_year=1956)
G(cls.model, course_id=cls.course_id, date=cls.date - datetime.timedelta(days=10), birth_year=1986) def format_as_response(self, *args):
def get_expected_response(self, *args):
return [ return [
{'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT), {'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT),
'birth_year': ce.birth_year} for ce in args] 'birth_year': ce.birth_year} for ce in args]
...@@ -196,51 +225,47 @@ class CourseEnrollmentByBirthYearViewTests(TestCaseWithAuthentication, CourseEnr ...@@ -196,51 +225,47 @@ class CourseEnrollmentByBirthYearViewTests(TestCaseWithAuthentication, CourseEnr
response = self.authenticated_get('/api/v0/courses/%s%s' % (self.course_id, self.path,)) response = self.authenticated_get('/api/v0/courses/%s%s' % (self.course_id, self.path,))
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
expected = self.get_expected_response(*self.model.objects.filter(date=self.date)) expected = self.format_as_response(*self.model.objects.filter(date=self.date))
self.assertEquals(response.data, expected) self.assertEquals(response.data, expected)
def test_get_with_intervals(self): def test_get_with_intervals(self):
expected = self.get_expected_response(*self.model.objects.filter(date=self.date)) expected = self.format_as_response(*self.model.objects.filter(date=self.date))
self.assertIntervalFilteringWorks(expected, self.date, self.date + datetime.timedelta(days=1)) self.assertIntervalFilteringWorks(expected, self.date, self.date + datetime.timedelta(days=1))
class CourseEnrollmentByEducationViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase): class CourseEnrollmentByEducationViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithAuthentication):
path = '/enrollment/education/' path = '/enrollment/education/'
model = models.CourseEnrollmentByEducation model = models.CourseEnrollmentByEducation
order_by = ['education_level'] order_by = ['education_level']
@classmethod def setUp(self):
def setUpClass(cls): super(CourseEnrollmentByEducationViewTests, self).setUp()
cls.el1 = G(models.EducationLevel, name='Doctorate', short_name='doctorate') self.el1 = G(models.EducationLevel, name='Doctorate', short_name='doctorate')
cls.el2 = G(models.EducationLevel, name='Top Secret', short_name='top_secret') self.el2 = G(models.EducationLevel, name='Top Secret', short_name='top_secret')
cls.course_id = 'edX/DemoX/Demo_Course' G(self.model, course_id=self.course_id, date=self.date, education_level=self.el1)
cls.date = datetime.date(2014, 1, 1) G(self.model, course_id=self.course_id, date=self.date, education_level=self.el2)
G(cls.model, course_id=cls.course_id, date=cls.date, education_level=cls.el1) G(self.model, course_id=self.course_id, date=self.date - datetime.timedelta(days=2),
G(cls.model, course_id=cls.course_id, date=cls.date, education_level=cls.el2) education_level=self.el2)
G(cls.model, course_id=cls.course_id, date=cls.date - datetime.timedelta(days=2),
education_level=cls.el2) def format_as_response(self, *args):
def get_expected_response(self, *args):
return [ return [
{'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT), {'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT),
'education_level': {'name': ce.education_level.name, 'short_name': ce.education_level.short_name}} for 'education_level': {'name': ce.education_level.name, 'short_name': ce.education_level.short_name}} for
ce in args] ce in args]
class CourseEnrollmentByGenderViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase): class CourseEnrollmentByGenderViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithAuthentication):
path = '/enrollment/gender/' path = '/enrollment/gender/'
model = models.CourseEnrollmentByGender model = models.CourseEnrollmentByGender
order_by = ['gender'] order_by = ['gender']
@classmethod def setUp(self):
def setUpClass(cls): super(CourseEnrollmentByGenderViewTests, self).setUp()
cls.course_id = 'edX/DemoX/Demo_Course' G(self.model, course_id=self.course_id, gender='m', date=self.date, count=34)
cls.date = datetime.date(2014, 1, 1) G(self.model, course_id=self.course_id, gender='f', date=self.date, count=45)
G(cls.model, course_id=cls.course_id, gender='m', date=cls.date, count=34) G(self.model, course_id=self.course_id, gender='f', date=self.date - datetime.timedelta(days=2), count=45)
G(cls.model, course_id=cls.course_id, gender='f', date=cls.date, count=45)
G(cls.model, course_id=cls.course_id, gender='f', date=cls.date - datetime.timedelta(days=2), count=45)
def get_expected_response(self, *args): def format_as_response(self, *args):
return [ return [
{'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT), {'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT),
'gender': ce.gender} for ce in args] 'gender': ce.gender} for ce in args]
...@@ -277,28 +302,26 @@ class AnswerDistributionTests(TestCaseWithAuthentication): ...@@ -277,28 +302,26 @@ class AnswerDistributionTests(TestCaseWithAuthentication):
self.assertEquals(response.status_code, 404) self.assertEquals(response.status_code, 404)
class CourseEnrollmentViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase): class CourseEnrollmentViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithAuthentication):
model = models.CourseEnrollmentDaily model = models.CourseEnrollmentDaily
path = '/enrollment' path = '/enrollment'
@classmethod def setUp(self):
def setUpClass(cls): super(CourseEnrollmentViewTests, self).setUp()
cls.course_id = 'edX/DemoX/Demo_Course' G(self.model, course_id=self.course_id, date=self.date, count=203)
cls.date = datetime.date(2014, 1, 1) G(self.model, course_id=self.course_id, date=self.date - datetime.timedelta(days=5), count=203)
G(cls.model, course_id=cls.course_id, date=cls.date, count=203)
G(cls.model, course_id=cls.course_id, date=cls.date - datetime.timedelta(days=5), count=203)
def get_expected_response(self, *args): def format_as_response(self, *args):
return [ return [
{'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT)} {'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT)}
for ce in args] for ce in args]
class CourseEnrollmentByLocationViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase): class CourseEnrollmentByLocationViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithAuthentication):
path = '/enrollment/location/' path = '/enrollment/location/'
model = models.CourseEnrollmentByCountry model = models.CourseEnrollmentByCountry
def get_expected_response(self, *args): def format_as_response(self, *args):
args = [arg for arg in args if arg.country_code not in ['', 'A1', 'A2', 'AP', 'EU', 'O1', 'UNKNOWN']] args = [arg for arg in args if arg.country_code not in ['', 'A1', 'A2', 'AP', 'EU', 'O1', 'UNKNOWN']]
args = sorted(args, key=lambda item: (item.date, item.course_id, item.country.alpha3)) args = sorted(args, key=lambda item: (item.date, item.course_id, item.country.alpha3))
return [ return [
...@@ -306,17 +329,15 @@ class CourseEnrollmentByLocationViewTests(TestCaseWithAuthentication, CourseEnro ...@@ -306,17 +329,15 @@ class CourseEnrollmentByLocationViewTests(TestCaseWithAuthentication, CourseEnro
'country': {'alpha2': ce.country.alpha2, 'alpha3': ce.country.alpha3, 'name': ce.country.name}} for ce in 'country': {'alpha2': ce.country.alpha2, 'alpha3': ce.country.alpha3, 'name': ce.country.name}} for ce in
args] args]
@classmethod def setUp(self):
def setUpClass(cls): super(CourseEnrollmentByLocationViewTests, self).setUp()
cls.course_id = 'edX/DemoX/Demo_Course' self.country = countries.get('US')
cls.date = datetime.date(2014, 1, 1) G(self.model, course_id=self.course_id, country_code='US', count=455, date=self.date)
cls.country = countries.get('US') G(self.model, course_id=self.course_id, country_code='CA', count=356, date=self.date)
G(cls.model, course_id=cls.course_id, country_code='US', count=455, date=cls.date) G(self.model, course_id=self.course_id, country_code='IN', count=12, date=self.date - datetime.timedelta(days=29))
G(cls.model, course_id=cls.course_id, country_code='CA', count=356, date=cls.date) G(self.model, course_id=self.course_id, country_code='', count=356, date=self.date)
G(cls.model, course_id=cls.course_id, country_code='IN', count=12, date=cls.date - datetime.timedelta(days=29)) G(self.model, course_id=self.course_id, country_code='A1', count=1, date=self.date)
G(cls.model, course_id=cls.course_id, country_code='', count=356, date=cls.date) G(self.model, course_id=self.course_id, country_code='A2', count=2, date=self.date)
G(cls.model, course_id=cls.course_id, country_code='A1', count=1, date=cls.date) G(self.model, course_id=self.course_id, country_code='AP', count=1, date=self.date)
G(cls.model, course_id=cls.course_id, country_code='A2', count=2, date=cls.date) G(self.model, course_id=self.course_id, country_code='EU', count=4, date=self.date)
G(cls.model, course_id=cls.course_id, country_code='AP', count=1, date=cls.date) G(self.model, course_id=self.course_id, country_code='O1', count=7, date=self.date)
G(cls.model, course_id=cls.course_id, country_code='EU', count=4, date=cls.date)
G(cls.model, course_id=cls.course_id, country_code='O1', count=7, date=cls.date)
...@@ -11,3 +11,4 @@ pep257==0.3.2 ...@@ -11,3 +11,4 @@ pep257==0.3.2
pep8==1.5.7 pep8==1.5.7
pylint==1.2.1 pylint==1.2.1
pytz==2012h pytz==2012h
nose-ignore-docstring==0.2
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