User Model and it's failure
I've started working on Users-API feature #22. Found that the Token-Authentication blocked api-docs - so I've created bug #39. I've temporarily commented out Token-Authentication and begun my work on User-API.
Then to my surprise ... User's API didn't properly handle bmi
and the bmi-health-name
.
So I've figure out it had to be some issue on Serializer site or on the QuerySet.
But no such luck, looking up this in internet lead me to some strange source-code changes that did nothing.
So I've dig a little deeper and debug the BMI algorithm itself
. And voila! There was the problem!
As it turns out, yesterday I've made some testing for User model and for bmi-health-name
function. While making that, I've used Mocking - which is in a general way a good idea. But to make use of Mocking you should be sure (having proper working tests!) that method/function you are mocking will output proper value. I didn't learn that lesson properly :)
So the BMI-Algorithm returned 0 all the time. When I've figure out where was the problem all other elements start ticking as they should !
As it turns out - I didn't learned lesson about making Unit-Tests independent from source-code - why ? Because of my laziness
I've put some math-algorithm inside of test - instead of using hard-coded value.
When I've finally found it out- I've change code and then tests started to fail with wrong output as expected. I've fixed that :)
Now what about that User API ?
Well, when changing all of this things I've said before, I've changed the standard Serializer
into a ModelSerializer
. I also added Meta
as described in ModelSerializer.
Then I've found that there is not tests for User Create/Update/Delete and retrieving list is not handled as it should.
So I've put effort into making best of this tests that are a coverage for User-API.
I also found bug #40 - when making tests for updating user.
Final result
Source Code of User API model:
class User(models.Model):
"""
User model for obtaining personal information about biking riders
"""
name = models.CharField(max_length=50)
surname = models.CharField(max_length=100, default="")
weight = models.IntegerField(default=0)
height = models.IntegerField(default=0)
def __unicode__(self):
"""
Returns User information when using str/printing
"""
return self.name
def bmi(self):
"""
Body Mass Index calculator simplified to number
"""
return self.weight / ((self.height * self.height) / 10000)
def bmi_health_name(self):
"""
BMI Health Name - Returns proper naming for value of BMI
"""
if self.bmi() < 0:
return None
if self.bmi() >= 0 and self.bmi() <= 18.5:
return "Underweight"
if self.bmi() > 18.5 and self.bmi() <= 24.9:
return "Normal weight"
if self.bmi() > 24.9 and self.bmi() <= 29.9:
return "Overweight"
if self.bmi() > 29.9:
return "Obesity"
Source Code tests of User API:
"""
Tests for API
"""
import unittest
import json
from api.serializers import UserSerializer
from api.views import UserList, UserDetail
from web.models import User
from django.contrib.auth.models import User as AuthUser
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient
from rest_framework.test import APIRequestFactory
from rest_framework.test import force_authenticate
class APIGeneralTestCase(unittest.TestCase):
"General Test Case for API"
def setUp(self):
self.path = "/api/user/"
self.client = APIClient()
self.user = AuthUser.objects.create_superuser('admin', 'admin@admin.com', 'admin123')
self.token = Token.objects.get(user_id=self.user.id).key
self.api = APIRequestFactory().get
self.view = UserList.as_view()
def tearDown(self):
"Removes all AuthUser and Users objects at the end of each test"
self.user.delete()
User.objects.all().delete()
def get_response_user_api(self, api_data=None, pk_id=None):
" Creates response for /api/user List get with forcing login with Token-Authentication "
self.client.force_login(user=self.user)
request = self.api(
self.path,
api_data,
HTTP_AUTHORIZATION='Token {}'.format(self.token),
content_type='application/json'
)
force_authenticate(request)
if pk_id is not None:
return self.view(request, pk=pk_id)
return self.view(request)
def assert_status_code(self, response):
" Asserts response status code with 200"
self.assertEquals(response.status_code, 200)
# pylint: disable=no-self-use
def create_user_in_db(self):
" Creates new user object in database "
return User.objects.create(name='Bart', surname="Trab", weight=80, height=175)
class TestUserSerializer(unittest.TestCase):
"UserSerializer tests"
def test_user_serialize_to_json(self):
"test if serializing to JSON works"
mocked = User()
mocked.name = "Anselmos"
mocked.surname = "Somlesna"
mocked.weight = 80
mocked.height = 175
user_serialized = UserSerializer(mocked)
self.assertEqual(
(user_serialized.data),
{
'height': 175,
'surname': u'Somlesna',
'id': None,
'weight': 80,
'name': u'Anselmos',
'bmi': 26,
'bmi_health_name': u'Overweight'
}
)
class TestUserList(APIGeneralTestCase):
"UserList tests"
def test_user_get_return_json(self):
"test if using get returns json data"
self.client.force_login(user=self.user)
response = self.get_response_user_api()
self.assert_status_code(response)
def test_user_list_return_json_list(self):
" Test if setting up user returns user list"
input_user = self.create_user_in_db()
self.client.force_login(user=self.user)
response = self.get_response_user_api()
self.assert_status_code(response)
self.assertEquals(len(response.data), 1)
self.assert_user_object(response.data[0], input_user)
def test_get_two_user_list(self):
" Test if setting up user returns user list"
input_user = self.create_user_in_db()
input_user2 = User.objects.create(name='B222art', surname="Trabaaaa", weight=50, height=100)
response = self.get_response_user_api()
self.assert_status_code(response)
self.assertEquals(len(response.data), 2)
self.assert_user_object(response.data[0], input_user)
self.assert_user_object(response.data[1], input_user2)
def assert_user_object(self, input_json, expected):
"Asserts response User input json object == expected user model object. "
self.assertEquals(input_json['name'], expected.name)
self.assertEquals(input_json['surname'], expected.surname)
self.assertEquals(input_json['weight'], expected.weight)
self.assertEquals(input_json['height'], expected.height)
self.assertEquals(input_json['bmi'], expected.bmi())
self.assertEquals(input_json['bmi_health_name'], expected.bmi_health_name())
class TestPostUser(APIGeneralTestCase):
"User Post tests"
def setUp(self):
super(self.__class__, self).setUp()
self.api = APIRequestFactory().post
def test_return_user_object(self):
" Tests if making api request returns user object "
data = json.dumps(
{"name":"Test", "surname":"tester1", "weight":88, "height":173}
)
response = self.get_response_user_api(data)
self.assertEquals(response.status_code, 201)
class TestDeleteUser(APIGeneralTestCase):
"User Delete tests"
def setUp(self):
super(self.__class__, self).setUp()
self.api = APIRequestFactory().delete
self.view = UserDetail.as_view()
def test_delete_user(self):
" Tests if making api request deletes user with 204"
user = self.create_user_in_db()
self.path = "/api/user/{}/".format(user.id)
response = self.get_response_user_api(pk_id=user.id)
self.assertEquals(response.status_code, 204)
class TestUpdateUser(APIGeneralTestCase):
"User Update tests"
def setUp(self):
super(self.__class__, self).setUp()
self.api = APIRequestFactory().post
self.view = UserList.as_view()
def test_update_user(self):
" Tests if making api request updates user with new name"
user = self.create_user_in_db()
self.path = "/api/user/{}/".format(user.id)
response = self.get_response_user_api(
json.dumps({"name": "newName", "weight": 88, "height": 111}),
pk_id=user.id
)
self.assertEquals(response.status_code, 201)
Code commits done for this post:
- Refactors code a bit. Changes few comments.
- Adds Update checker for User API
- Refactors code. Changes names to proper one.
- Refactors code. Adds Delete-API test for User API
- Refactors code
- Refactors code.
- Refactors code. Creates APIGeneralTestCase
- Adds test for creating user with API
- Refactors code.
- Adds tests for checking if user-list contains element from api
- Fixes #22. Fixes User-API and bmi-algorithm.
Release:
Tools and applications used:
- Vi/VIM
- Docker
- Tmux
- anselmos/dotfiles
- anselmos/linux-utils
- Tomatoid
Acknowledgements of links I've found usefull while writing this article:
Others:
Django-Rest-Framework:
StackOverFlow:
- Understanding Python super() with init() methods [duplicate]
- How to remove all of the data in a table using django
- Problems using User model in django unit tests
Accomplished:
Issue #22 - User API
Final word
I wanted to make all of the issues that are there for 1st Milestone. Unfortunatelly I already see that it's impossible to make 6 big features in 4 hours (even less).
I'll move end time till Wednesday and let's see how much progress I will make.
Next time I'll just have to create less optimistic estimation of time per issue :)
That's all, thanks ! Keep in touch :)
Comments
comments powered by Disqus