From 59659835e837aeb34c587999576047113c0bc448 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 3 Feb 2015 10:00:42 +0200 Subject: [PATCH] Added a basic benchmark suite for serializers. --- requirements.txt | 1 + runtests.py | 4 +- tests/fields.py | 10 +++ tests/models.py | 60 +++++++++++++++ tests/test_model_serializer.py | 40 +--------- tests/test_serializers_benchmarks.py | 107 +++++++++++++++++++++++++++ tox.ini | 1 + 7 files changed, 185 insertions(+), 38 deletions(-) create mode 100644 tests/fields.py create mode 100644 tests/test_serializers_benchmarks.py diff --git a/requirements.txt b/requirements.txt index 00d973cdf..10ef9790b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ Django>=1.4.11 pytest-django==2.8.0 pytest==2.6.4 pytest-cov==1.6 +pytest-bench==0.3.0 flake8==2.2.2 # Optional packages diff --git a/runtests.py b/runtests.py index 0008bfae5..040adacdc 100755 --- a/runtests.py +++ b/runtests.py @@ -8,8 +8,8 @@ import subprocess PYTEST_ARGS = { - 'default': ['tests', '--tb=short'], - 'fast': ['tests', '--tb=short', '-q'], + 'default': ['tests', '--tb=short', '--bench'], + 'fast': ['tests', '--tb=short', '-q', '--bench'], } FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501'] diff --git a/tests/fields.py b/tests/fields.py new file mode 100644 index 000000000..dafb21fce --- /dev/null +++ b/tests/fields.py @@ -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 diff --git a/tests/models.py b/tests/models.py index 456b0a0bb..b07664349 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django.db import models from django.utils.translation import ugettext_lazy as _ +from .fields import CustomField class RESTFrameworkModel(models.Model): @@ -68,3 +69,62 @@ class NullableOneToOneSource(RESTFrameworkModel): name = models.CharField(max_length=100) target = models.OneToOneField(OneToOneTarget, null=True, blank=True, 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 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' diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 247b309a1..37b578840 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -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. """ from __future__ import unicode_literals + from django.core.exceptions import ImproperlyConfigured from django.core.validators import MaxValueValidator, MinValueValidator, MinLengthValidator from django.db import models from django.test import TestCase from django.utils import six + from rest_framework import serializers from rest_framework.compat import unicode_repr +from .models import RegularFieldsModel def dedent(blocktext): @@ -22,46 +25,11 @@ def dedent(blocktext): # Tests for regular field mappings. # --------------------------------- -class CustomField(models.Field): - """ - A custom model field simply for testing purposes. - """ - pass - class OneFieldModel(models.Model): 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')) @@ -125,7 +93,7 @@ class TestRegularFieldMappings(TestCase): text_field = CharField(style={'base_template': 'textarea.html'}) time_field = TimeField() url_field = URLField(max_length=100) - custom_field = ModelField(model_field=) + custom_field = ModelField(model_field=) """) self.assertEqual(unicode_repr(TestSerializer()), expected) diff --git a/tests/test_serializers_benchmarks.py b/tests/test_serializers_benchmarks.py new file mode 100644 index 000000000..63af030f3 --- /dev/null +++ b/tests/test_serializers_benchmarks.py @@ -0,0 +1,107 @@ +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' +} + + +class TestSerializer(serializers.ModelSerializer): + class Meta: + model = RegularFieldsModel + fields = data.keys() + ['method'] + + +class TestNestedSerializer(serializers.ModelSerializer): + fk = TestSerializer() + + class Meta: + model = RegularFieldsAndFKModel + fields = data.keys() + ['method', 'fk'] + + +@mark.bench('serializers.ModelSerializer.to_representation') +def test_object_serialization(): + instance = RegularFieldsModel(**data) + serializer = TestSerializer(instance=instance) + + assert serializer.data, serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_representation') +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.ModelSerializer.to_representation') +def test_object_list_serialization(): + instance = [RegularFieldsModel(**data) for _ in range(100)] + serializer = TestSerializer(instance=instance, many=True) + + assert serializer.data, serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_representation') +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') +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') +def test_object_deserialization(): + serializer = TestSerializer(data=data) + + assert serializer.is_valid(), serializer.errors + + +@mark.bench('serializers.ModelSerializer.to_internal_value') +def test_object_list_deserialization(): + serializer = TestSerializer(data=[data for _ in range(100)], many=True) + + assert serializer.is_valid(), serializer.errors + + +@mark.bench('serializers.ModelSerializer.__init__') +def test_serializer_initialization(): + TestSerializer(data=data) diff --git a/tox.ini b/tox.ini index 8e0369643..f620faeb7 100644 --- a/tox.ini +++ b/tox.ini @@ -22,6 +22,7 @@ deps = {py26,py27}-django{14,15}: django-oauth2-provider==0.2.3 {py26,py27}-django16: django-oauth2-provider==0.2.4 pytest-django==2.8.0 + pytest-bench==0.3.0 django-filter==0.9.2 defusedxml==0.3 markdown>=2.1.0