This commit is contained in:
Omer Katz 2015-02-10 23:44:19 +00:00
commit 6048152c2c
9 changed files with 537 additions and 37 deletions

View File

@ -5,6 +5,7 @@ Django>=1.4.11
pytest-django==2.8.0 pytest-django==2.8.0
pytest==2.6.4 pytest==2.6.4
pytest-cov==1.6 pytest-cov==1.6
pytest-bench==0.3.0
flake8==2.2.2 flake8==2.2.2
# Optional packages # Optional packages

View File

@ -8,7 +8,7 @@ import subprocess
PYTEST_ARGS = { PYTEST_ARGS = {
'default': ['tests', '--tb=short'], 'default': ['tests', '--tb=short', '--bench'],
'fast': ['tests', '--tb=short', '-q'], 'fast': ['tests', '--tb=short', '-q'],
} }

10
tests/fields.py Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
# coding: utf-8
from django.db import models
class CustomField(models.Field):
"""
A custom model field simply for testing purposes.
"""
pass

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .fields import CustomField
class RESTFrameworkModel(models.Model): class RESTFrameworkModel(models.Model):
@ -68,3 +69,119 @@ class NullableOneToOneSource(RESTFrameworkModel):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
target = models.OneToOneField(OneToOneTarget, null=True, blank=True, target = models.OneToOneField(OneToOneTarget, null=True, blank=True,
related_name='nullable_source') related_name='nullable_source')
class RegularFieldsModel(models.Model):
"""
A model class for testing regular flat fields.
"""
auto_field = models.AutoField(primary_key=True)
big_integer_field = models.BigIntegerField()
boolean_field = models.BooleanField(default=False)
char_field = models.CharField(max_length=100)
comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100)
date_field = models.DateField()
datetime_field = models.DateTimeField()
decimal_field = models.DecimalField(max_digits=3, decimal_places=1)
email_field = models.EmailField(max_length=100)
float_field = models.FloatField()
integer_field = models.IntegerField()
null_boolean_field = models.NullBooleanField()
positive_integer_field = models.PositiveIntegerField()
positive_small_integer_field = models.PositiveSmallIntegerField()
slug_field = models.SlugField(max_length=100)
small_integer_field = models.SmallIntegerField()
text_field = models.TextField()
time_field = models.TimeField()
url_field = models.URLField(max_length=100)
custom_field = CustomField()
def method(self):
return 'method'
class RegularFieldsModel2(models.Model):
"""
A model class for testing regular flat fields.
"""
auto_field = models.AutoField(primary_key=True)
big_integer_field = models.BigIntegerField()
boolean_field = models.BooleanField(default=False)
char_field = models.CharField(max_length=100)
comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100)
date_field = models.DateField()
datetime_field = models.DateTimeField()
decimal_field = models.DecimalField(max_digits=3, decimal_places=1)
email_field = models.EmailField(max_length=100)
float_field = models.FloatField()
integer_field = models.IntegerField()
null_boolean_field = models.NullBooleanField()
positive_integer_field = models.PositiveIntegerField()
positive_small_integer_field = models.PositiveSmallIntegerField()
slug_field = models.SlugField(max_length=100)
small_integer_field = models.SmallIntegerField()
text_field = models.TextField()
time_field = models.TimeField()
url_field = models.URLField(max_length=100)
def method(self):
return 'method'
class RegularFieldsAndFKModel(models.Model):
"""
A model class for testing regular flat fields.
"""
auto_field = models.AutoField(primary_key=True)
big_integer_field = models.BigIntegerField()
boolean_field = models.BooleanField(default=False)
char_field = models.CharField(max_length=100)
comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100)
date_field = models.DateField()
datetime_field = models.DateTimeField()
decimal_field = models.DecimalField(max_digits=3, decimal_places=1)
email_field = models.EmailField(max_length=100)
float_field = models.FloatField()
integer_field = models.IntegerField()
null_boolean_field = models.NullBooleanField()
positive_integer_field = models.PositiveIntegerField()
positive_small_integer_field = models.PositiveSmallIntegerField()
slug_field = models.SlugField(max_length=100)
small_integer_field = models.SmallIntegerField()
text_field = models.TextField()
time_field = models.TimeField()
url_field = models.URLField(max_length=100)
custom_field = CustomField()
fk = models.ForeignKey(RegularFieldsModel)
def method(self):
return 'method'
class RegularFieldsAndFKModel2(models.Model):
"""
A model class for testing regular flat fields.
"""
auto_field = models.AutoField(primary_key=True)
big_integer_field = models.BigIntegerField()
boolean_field = models.BooleanField(default=False)
char_field = models.CharField(max_length=100)
comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100)
date_field = models.DateField()
datetime_field = models.DateTimeField()
decimal_field = models.DecimalField(max_digits=3, decimal_places=1)
email_field = models.EmailField(max_length=100)
float_field = models.FloatField()
integer_field = models.IntegerField()
null_boolean_field = models.NullBooleanField()
positive_integer_field = models.PositiveIntegerField()
positive_small_integer_field = models.PositiveSmallIntegerField()
slug_field = models.SlugField(max_length=100)
small_integer_field = models.SmallIntegerField()
text_field = models.TextField()
time_field = models.TimeField()
url_field = models.URLField(max_length=100)
fk = models.ForeignKey(RegularFieldsModel2)
def method(self):
return 'method'

View File

@ -0,0 +1,204 @@
#!/usr/bin/env python
# coding: utf-8
from decimal import Decimal
from datetime import datetime
from django.utils import unittest
from pytest import mark
from rest_framework import viewsets, serializers
from rest_framework.filters import DjangoFilterBackend
from rest_framework.reverse import reverse
from rest_framework.routers import DefaultRouter
from rest_framework.test import APITransactionTestCase
from tests.models import RegularFieldsAndFKModel2, RegularFieldsModel2
data = {
'big_integer_field': 100000,
'char_field': 'a',
'comma_separated_integer_field': '1,2',
'date_field': str(datetime.now().date()),
'datetime_field': str(datetime.now()),
'decimal_field': str(Decimal('1.5')),
'email_field': 'somewhere@overtherainbow.com',
'float_field': 0.443,
'integer_field': 55,
'null_boolean_field': True,
'positive_integer_field': 1,
'positive_small_integer_field': 1,
'slug_field': 'slug-friendly-text',
'small_integer_field': 1,
'text_field': 'lorem ipsum',
'time_field': str(datetime.now().time()),
'url_field': 'https://overtherainbow.com'
}
data_list = [data for _ in range(100)]
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel2
fields = list(data.keys()) + ['method']
class TestNestedSerializer(serializers.ModelSerializer):
fk = TestSerializer()
class Meta:
model = RegularFieldsAndFKModel2
fields = list(data.keys()) + ['fk', 'method']
def create(self, validated_data):
fk = RegularFieldsModel2.objects.create(**validated_data['fk'])
validated_data['fk'] = fk
return RegularFieldsAndFKModel2.objects.create(**validated_data)
def update(self, instance, validated_data):
fk_data = validated_data.pop('fk')
fk_pk = fk_data.pop('auto_field', None)
if not fk_pk:
fk_pk = instance.fk_id
RegularFieldsModel2.objects.filter(pk=fk_pk).update(**fk_data)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.fk_id = fk_pk
instance.save()
return instance
class RegularFieldsAndFKViewSet(viewsets.ModelViewSet):
queryset = RegularFieldsAndFKModel2.objects.all()
serializer_class = TestNestedSerializer
class FilteredRegularFieldsAndFKViewSet(RegularFieldsAndFKViewSet):
filter_backends = (DjangoFilterBackend,)
filter_fields = ('big_integer_field',)
router = DefaultRouter()
router.register('benchmark', RegularFieldsAndFKViewSet, base_name='benchmark')
router.register('benchmark2', FilteredRegularFieldsAndFKViewSet, base_name='benchmark-filter')
urlpatterns = router.urls
class FullStackBenchmarksTestCase(APITransactionTestCase):
urls = 'tests.test_full_stack_benchmarks'
def setUp(self):
RegularFieldsModel2.objects.bulk_create([RegularFieldsModel2(**d) for d in data_list])
RegularFieldsAndFKModel2.objects.bulk_create(
[RegularFieldsAndFKModel2(fk=o, **data) for o in RegularFieldsModel2.objects.all()])
self.first_pk = RegularFieldsAndFKModel2.objects.only('pk').first().pk
self.last_pk = RegularFieldsAndFKModel2.objects.only('pk').last().pk
@mark.bench('viewsets.ModelViewSet.list', iterations=1000)
def test_viewset_list(self):
url = reverse('benchmark-list')
response = self.client.get(url)
assert response.status_code == 200, (response.rendered_content, url)
@mark.bench('viewsets.ModelViewSet.retrieve', iterations=10000)
def test_viewset_retrieve(self):
url = reverse('benchmark-detail', args=[self.first_pk])
response = self.client.get(url)
assert response.status_code == 200, (response.rendered_content, url)
@mark.bench('viewsets.ModelViewSet.create', iterations=1000)
def test_viewset_create(self):
url = reverse('benchmark-list')
new_data = data.copy()
new_data['fk'] = data.copy()
response = self.client.post(url, data=new_data, format='json')
assert response.status_code == 201, (response.rendered_content, url)
@mark.bench('viewsets.ModelViewSet.update', iterations=1000)
def test_viewset_update(self):
url = reverse('benchmark-detail', args=[self.first_pk])
new_data = data.copy()
new_fk = RegularFieldsModel2.objects.create(**data)
new_fk_data = data.copy()
new_fk_data['auto_field'] = new_fk.pk
new_data['fk'] = new_fk_data
response = self.client.put(url, data=new_data, format='json')
assert response.status_code == 200, (response.rendered_content, url)
@mark.bench('viewsets.ModelViewSet.partial_update', iterations=1000)
def test_viewset_partial_update(self):
url = reverse('benchmark-detail', args=[self.first_pk])
new_fk = RegularFieldsModel2.objects.create(**data)
new_fk_data = data.copy()
new_fk_data['auto_field'] = new_fk.pk
new_data = {'fk': new_fk_data}
response = self.client.patch(url, data=new_data)
assert response.status_code == 200, (response.rendered_content, url)
@mark.bench('viewsets.ModelViewSet.destroy', iterations=10000)
def test_viewset_delete(self):
new_fk = RegularFieldsModel2.objects.create(**data)
new_obj = RegularFieldsAndFKModel2.objects.create(fk=new_fk, **data)
url = reverse('benchmark-detail', args=[new_obj.pk])
response = self.client.delete(url)
assert response.status_code == 204, (response.rendered_content, url)
class FullStackFilteredBenchmarksTestCase(APITransactionTestCase):
urls = 'tests.test_full_stack_benchmarks'
def setUp(self):
RegularFieldsModel2.objects.bulk_create([RegularFieldsModel2(**d) for d in data_list])
RegularFieldsAndFKModel2.objects.bulk_create(
[RegularFieldsAndFKModel2(fk=o, **data) for o in RegularFieldsModel2.objects.all()])
self.first_pk = RegularFieldsAndFKModel2.objects.only('pk').first().pk
self.last_pk = RegularFieldsAndFKModel2.objects.only('pk').last().pk
@mark.bench('viewsets.ModelViewSet.list', iterations=1000)
def test_viewset_list(self):
url = reverse('benchmark-filter-list')
response = self.client.get(url, data={'big_integer_field': 100000})
assert response.status_code == 200, (response.rendered_content, url)
@mark.bench('viewsets.ModelViewSet.retrieve', iterations=10000)
def test_viewset_retrieve(self):
url = reverse('benchmark-filter-detail', args=[self.first_pk])
response = self.client.get(url, data={'big_integer_field': 100000})
assert response.status_code == 200, (response.rendered_content, url)
@mark.bench('viewsets.ModelViewSet.list', iterations=1000)
def test_viewset_list_nothing(self):
url = reverse('benchmark-filter-list')
response = self.client.get(url, data={'big_integer_field': 100001})
assert response.rendered_content == '[]', (response.rendered_content, url)
@mark.bench('viewsets.ModelViewSet.retrieve', iterations=10000)
@unittest.skip('pytest-bench cannot benchmark operations that raise exceptions')
def test_viewset_retrieve_nothing(self):
url = reverse('benchmark-filter-detail', args=[self.first_pk])
response = self.client.get(url, data={'big_integer_field': 100001})
assert response.status_code == 404, (response.rendered_content, url)

View File

@ -6,13 +6,16 @@ These tests deal with ensuring that we correctly map the model fields onto
an appropriate set of serializer fields for each case. an appropriate set of serializer fields for each case.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.validators import MaxValueValidator, MinValueValidator, MinLengthValidator from django.core.validators import MaxValueValidator, MinValueValidator, MinLengthValidator
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from django.utils import six from django.utils import six
from rest_framework import serializers from rest_framework import serializers
from rest_framework.compat import unicode_repr from rest_framework.compat import unicode_repr
from .models import RegularFieldsModel
def dedent(blocktext): def dedent(blocktext):
@ -22,46 +25,11 @@ def dedent(blocktext):
# Tests for regular field mappings. # Tests for regular field mappings.
# --------------------------------- # ---------------------------------
class CustomField(models.Field):
"""
A custom model field simply for testing purposes.
"""
pass
class OneFieldModel(models.Model): class OneFieldModel(models.Model):
char_field = models.CharField(max_length=100) char_field = models.CharField(max_length=100)
class RegularFieldsModel(models.Model):
"""
A model class for testing regular flat fields.
"""
auto_field = models.AutoField(primary_key=True)
big_integer_field = models.BigIntegerField()
boolean_field = models.BooleanField(default=False)
char_field = models.CharField(max_length=100)
comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100)
date_field = models.DateField()
datetime_field = models.DateTimeField()
decimal_field = models.DecimalField(max_digits=3, decimal_places=1)
email_field = models.EmailField(max_length=100)
float_field = models.FloatField()
integer_field = models.IntegerField()
null_boolean_field = models.NullBooleanField()
positive_integer_field = models.PositiveIntegerField()
positive_small_integer_field = models.PositiveSmallIntegerField()
slug_field = models.SlugField(max_length=100)
small_integer_field = models.SmallIntegerField()
text_field = models.TextField()
time_field = models.TimeField()
url_field = models.URLField(max_length=100)
custom_field = CustomField()
def method(self):
return 'method'
COLOR_CHOICES = (('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')) COLOR_CHOICES = (('red', 'Red'), ('blue', 'Blue'), ('green', 'Green'))
@ -125,7 +93,7 @@ class TestRegularFieldMappings(TestCase):
text_field = CharField(style={'base_template': 'textarea.html'}) text_field = CharField(style={'base_template': 'textarea.html'})
time_field = TimeField() time_field = TimeField()
url_field = URLField(max_length=100) url_field = URLField(max_length=100)
custom_field = ModelField(model_field=<tests.test_model_serializer.CustomField: custom_field>) custom_field = ModelField(model_field=<tests.fields.CustomField: custom_field>)
""") """)
self.assertEqual(unicode_repr(TestSerializer()), expected) self.assertEqual(unicode_repr(TestSerializer()), expected)

View File

@ -0,0 +1,33 @@
from decimal import Decimal
from datetime import datetime
from pytest import mark
from rest_framework import renderers
data = {
'big_integer_field': 100000,
'char_field': 'a',
'comma_separated_integer_field': '1,2',
'date_field': datetime.now().date(),
'datetime_field': datetime.now(),
'decimal_field': Decimal('1.5'),
'email_field': 'somewhere@overtherainbow.com',
'float_field': 0.443,
'integer_field': 55,
'null_boolean_field': True,
'positive_integer_field': 1,
'positive_small_integer_field': 1,
'slug_field': 'slug-friendly-text',
'small_integer_field': 1,
'text_field': 'lorem ipsum',
'time_field': datetime.now().time(),
'url_field': 'https://overtherainbow.com'
}
@mark.bench('renderers.JSONRenderer.render', iterations=1000000)
def test_json_renderer():
renderer = renderers.JSONRenderer()
renderer.render(data)

View File

@ -0,0 +1,166 @@
from decimal import Decimal
from pytest import mark
from datetime import datetime
from rest_framework import serializers
from .models import RegularFieldsModel, RegularFieldsAndFKModel
data = {
'big_integer_field': 100000,
'char_field': 'a',
'comma_separated_integer_field': '1,2',
'date_field': datetime.now().date(),
'datetime_field': datetime.now(),
'decimal_field': Decimal('1.5'),
'email_field': 'somewhere@overtherainbow.com',
'float_field': 0.443,
'integer_field': 55,
'null_boolean_field': True,
'positive_integer_field': 1,
'positive_small_integer_field': 1,
'slug_field': 'slug-friendly-text',
'small_integer_field': 1,
'text_field': 'lorem ipsum',
'time_field': datetime.now().time(),
'url_field': 'https://overtherainbow.com'
}
nested_data = {
'big_integer_field': 100000,
'char_field': 'a',
'comma_separated_integer_field': '1,2',
'date_field': datetime.now().date(),
'datetime_field': datetime.now(),
'decimal_field': Decimal('1.5'),
'email_field': 'somewhere@overtherainbow.com',
'float_field': 0.443,
'integer_field': 55,
'null_boolean_field': True,
'positive_integer_field': 1,
'positive_small_integer_field': 1,
'slug_field': 'slug-friendly-text',
'small_integer_field': 1,
'text_field': 'lorem ipsum',
'time_field': datetime.now().time(),
'url_field': 'https://overtherainbow.com',
'fk': data
}
data_list = [data for _ in range(100)]
data_list_with_nesting = [nested_data for _ in range(100)]
instances_list = [RegularFieldsModel(**data) for _ in range(100)]
instances_with_nesting = [RegularFieldsAndFKModel(fk=nested_instance, **data) for nested_instance in instances_list]
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = list(data.keys()) + ['method']
class TestNestedSerializer(serializers.ModelSerializer):
fk = TestSerializer()
class Meta:
model = RegularFieldsAndFKModel
fields = list(data.keys()) + ['method', 'fk']
@mark.bench('serializers.ModelSerializer.get_fields', iterations=1000)
def test_get_fields():
instance = RegularFieldsModel(**data)
serializer = TestSerializer(instance=instance)
assert serializer.get_fields()
@mark.bench('serializers.ModelSerializer.get_fields', iterations=1000)
def test_get_fields_twice():
instance = RegularFieldsModel(**data)
serializer = TestSerializer(instance=instance)
assert serializer.get_fields()
assert serializer.get_fields()
@mark.bench('serializers.ModelSerializer.to_representation', iterations=1000)
def test_object_serialization():
instance = RegularFieldsModel(**data)
serializer = TestSerializer(instance=instance)
assert serializer.data, serializer.errors
@mark.bench('serializers.ModelSerializer.to_representation', iterations=1000)
def test_nested_object_serialization():
nested_instance = RegularFieldsModel(**data)
instance = RegularFieldsAndFKModel(fk=nested_instance, **data)
serializer = TestSerializer(instance=instance)
assert serializer.data, serializer.errors
@mark.bench('serializers.ListSerializer.to_representation', iterations=1000)
def test_object_list_serialization():
serializer = TestSerializer(instance=instances_list, many=True)
assert serializer.data, serializer.errors
@mark.bench('serializers.ListSerializer.to_representation', iterations=1000)
def test_nested_object_list_serialization():
serializer = TestSerializer(instance=instances_with_nesting, many=True)
assert serializer.data, serializer.errors
@mark.bench('serializers.ModelSerializer.to_representation', iterations=10000)
def test_object_serialization_with_partial_update():
instance = RegularFieldsModel(**data)
serializer = TestSerializer(instance=instance, data={'char_field': 'b'}, partial=True)
assert serializer.is_valid(), serializer.errors
assert serializer.data, serializer.errors
@mark.bench('serializers.ModelSerializer.to_representation', iterations=10000)
def test_object_serialization_with_update():
instance = RegularFieldsModel(**data)
new_data = data.copy()
new_data['char_field'] = 'b'
serializer = TestSerializer(instance=instance, data=new_data)
assert serializer.is_valid(), serializer.errors
assert serializer.data, serializer.errors
@mark.bench('serializers.ModelSerializer.to_internal_value', iterations=1000)
def test_object_deserialization():
serializer = TestSerializer(data=data)
assert serializer.is_valid(), serializer.errors
@mark.bench('serializers.ListSerializer.to_internal_value', iterations=1000)
def test_object_list_deserialization():
serializer = TestSerializer(data=data_list, many=True)
assert serializer.is_valid(), serializer.errors
@mark.bench('serializers.ListSerializer.to_internal_value', iterations=1000)
def test_nested_object_list_deserialization():
serializer = TestSerializer(data=data_list_with_nesting, many=True)
assert serializer.is_valid(), serializer.errors
@mark.bench('serializers.ModelSerializer.__init__', iterations=10000)
def test_serializer_initialization():
TestSerializer(data=data)

View File

@ -21,6 +21,7 @@ deps =
{py26,py27}-django{14,15}: django-oauth2-provider==0.2.3 {py26,py27}-django{14,15}: django-oauth2-provider==0.2.3
{py26,py27}-django16: django-oauth2-provider==0.2.4 {py26,py27}-django16: django-oauth2-provider==0.2.4
pytest-django==2.8.0 pytest-django==2.8.0
pytest-bench==0.3.0
django-filter==0.9.2 django-filter==0.9.2
defusedxml==0.3 defusedxml==0.3
markdown>=2.1.0 markdown>=2.1.0