Rolex Pearlmaster Replica
  Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
This article is part of in the series
Published: Saturday 17th August 2013
Last Updated: Thursday 12th December 2013

Tests, Tests and More Tests

As a software programmer, you often hear others talking about tests being one of the most important components of any project. A software project often succeeds when there's proper test coverage. While it often fails when there's little or none. You might be wondering: what are tests anyway? Why is everyone constantly emphasizing its importance?

Tests are simple routines or mini-programs that check the correctness and completeness of your code. Some tests check tiny details of a software project - does a particular Django model get updated when a POST method is called?, while others check the overall operation of the software - does the code execute my business logic about customer order submission correctly?. No matter how small, every test is important since it tells you whether your code is broken or not. Although having 100% test coverage of your code is pretty hard and takes a significant amount of effort, you should always try to cover as much of your code with tests as possible.

Overall, tests:

  • Save you time since they allow you to test the application without manually running the code all the time.
  • Help you verify and clarify the software requirements since they force you to think about the problem at hand and write proper tests to prove that the solution works.
  • Make your code more robust and attractive to others since they enable any reader to see your code has been proved to be working correctly under tests.
  • Help teams work together since they allow teammates to verify each other's code by writing tests against the code.

Write Your First Automated Django Test

In our existing application myblog's index views, we're returning recent posts made by users less than two days from now. The code of the index view is attached here:

[python]
def index(request):
two_days_ago = datetime.utcnow() - timedelta(days=2)
recent_posts = m.Post.objects.filter(created_at__gt=two_days_ago).all()
context = Context({
'post_list': recent_posts
})
return render(request, 'index.html', context)
[/python]

There's a little bug in this view. Can you find it?

It seems that we're assuming that all posts in our website are "posted" in the past, namely that the Post.created_at is earlier than timezone.now(). However, it's highly possible that a user prepared a post in advance and wants to publish it in a future datetime instead. Obviously, the current code will also return those future posts. It can be verified in the following snippet:

[python]
>>> m.Post.objects.all().delete()
>>> import datetime
>>> from django.utils import timezone
>>> from myblog import models as m
>>> future_post = m.Post(content='Future Post',
>>> created_at=timezone.now() + datetime.timedelta(days=10))
>>> future_post.save()
>>> two_days_ago = datetime.datetime.utcnow() - datetime.timedelta(days=2)
>>> recent_posts = m.Post.objects.filter(created_at__gt=two_days_ago).all()
# recent_posts contain future_post, which is wrong.
>>> recent_posts[0].content
u'Future Post'
[/python]

Before we go ahead and fix the bug in the view, let's pause a little bit and write a test to expose this bug. First, we add a new method recent_posts() onto the model Post so we could extract the incorrect code out of the view:

[python]
import datetime

from django.db import models as m
from django.utils import timezone

class Post(m.Model):
content = m.CharField(max_length=256)
created_at = m.DateTimeField('datetime created')

@classmethod
def recent_posts(cls):
two_days_ago = timezone.now() - datetime.timedelta(days=2)
return Post.objects.filter(created_at__gt=two_days_ago)
[/python]

Then, we modify the index view's code to use the recent_posts() method from the model Post:

[python]
def index(request):
recent_posts = m.Post.recent_posts()
context = Context({
'post_list': recent_posts
})
return render(request, 'index.html', context)
[/python]

Now we add the following code into myblog/tests.py so that we can run it to test the behaviour of our code:

[python]
import datetime

from django.utils import timezone
from django.test import TestCase

from myblog import models as m

class PostModelTests(TestCase):
def setUp(self):
''' Create a Post from the future. '''
super(PostModelTests, self).setUp()
self.future_post = m.Post(
content='Future Post', created_at=timezone.now() + datetime.timedelta(days=10))
self.future_post.save()

def tearDown(self):
''' Delete the post from the future. '''
super(PostModelTests, self).tearDown()
m.Post.objects.get(content='Future Post').delete()

def test_recent_posts_not_including_future_posts(self):
''' m.Post.recent_posts() should not return posts from the future. '''
recent_posts = m.Post.recent_posts()
self.assertNotIn(self.future_post, recent_posts)
[/python]

In this test case, we want to verify that future posts are not included in the list of posts returned from m.Post.recent_posts(). Now you can run the tests by:

[shell]
$ python manage.py test
Creating test database for alias 'default'...
....................................................
======================================================================
FAIL: test_recent_posts_not_including_future_posts (myblog.tests.PostModelTests)
m.Post.recent_posts() should not return posts from the future.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/user/python2-workspace/pythoncentral/django_series/article7/myblog/myblog/tests.py", line 23, in test_recent_posts_not_including_future_posts
self.assertNotIn(self.future_post, recent_posts)
AssertionError: unexpectedly found in []

----------------------------------------------------------------------
Ran 483 tests in 11.877s

FAILED (failures=1, skipped=1, expected failures=1)
Destroying test database for alias 'default'...
[/shell]

Since the post from the future is in the list returned from recent_posts() and our test complained about it, we know for sure there's a bug in our code.

Fix Our Test Case Bug

We can easily fix the bug by making sure that m.Post.created_at is earlier than timezone.now() in recent_posts()'s query:

[python]
class Post(m.Model):
content = m.CharField(max_length=256)
created_at = m.DateTimeField('datetime created')

@classmethod
def recent_posts(cls):
now = timezone.now()
two_days_ago = now - datetime.timedelta(days=2)
return Post.objects.\
filter(created_at__gt=two_days_ago). \
filter(created_at__lt=now)
[/python]

Now you can re-run the test and it should pass without warning:

[shell]
$ python manage.py test
Creating test database for alias 'default'...
.................................................................................................................................................s.....................................................................................................................................x...........................................................................................................................................................................................................
----------------------------------------------------------------------
Ran 483 tests in 12.725s

OK (skipped=1, expected failures=1)
Destroying test database for alias 'default'...
[/shell]

Automated Test Case Summary and Tips

In this article, we learned how to write automated tests for our first Django application. Since writing tests is one of the best software engineering practices, it always pays off. It may seem counter-intuitive since you have to write more code to implement the same functionality, but tests will save a lot of your time in the future.

When writing a Django application, we put our test code into tests.py and run them by running $ python manage.py test. If there's any test that did not pass, then Django reports the error back to us so we can fix any bug accordingly. If all tests passed, then Django shows that there's no error and we can be very confident that our code works. Therefore, Having a proper test coverage over your code is one of the best ways to write high quality software.

About The Author

Xiaonuo Gantan

Latest Articles


Tags

  • Unpickling
  • array
  • sorting
  • reversal
  • Python salaries
  • list sort
  • Pip
  • .groupby()
  • pyenv global
  • NumPy arrays
  • Modulo
  • OpenCV
  • Torrent
  • data
  • int function
  • file conversion
  • calculus
  • python typing
  • encryption
  • strings
  • big o calculator
  • gamin
  • HTML
  • list
  • insertion sort
  • in place reversal
  • learn python
  • String
  • python packages
  • FastAPI
  • argparse
  • zeros() function
  • AWS Lambda
  • Scikit Learn
  • Free
  • classes
  • turtle
  • convert file
  • abs()
  • python do while
  • set operations
  • data visualization
  • efficient coding
  • data analysis
  • HTML Parser
  • circular queue
  • effiiciency
  • Learning
  • windows
  • reverse
  • Python IDE
  • python maps
  • dataframes
  • Num Py Zeros
  • Python Lists
  • Fprintf
  • Version
  • immutable
  • python turtle
  • pandoc
  • semantic kernel
  • do while
  • set
  • tabulate
  • optimize code
  • object oriented
  • HTML Extraction
  • head
  • selection sort
  • Programming
  • install python on windows
  • reverse string
  • python Code Editors
  • Pytest
  • pandas.reset_index
  • NumPy
  • Infinite Numbers in Python
  • Python Readlines()
  • Trial
  • youtube
  • interactive
  • deep
  • kernel
  • while loop
  • union
  • tutorials
  • audio
  • github
  • Parsing
  • tail
  • merge sort
  • Programming language
  • remove python
  • concatenate string
  • Code Editors
  • unittest
  • reset_index()
  • Train Test Split
  • Local Testing Server
  • Python Input
  • Studio
  • excel
  • sgd
  • deeplearning
  • pandas
  • class python
  • intersection
  • logic
  • pydub
  • git
  • Scrapping
  • priority queue
  • quick sort
  • web development
  • uninstall python
  • python string
  • code interface
  • PyUnit
  • round numbers
  • train_test_split()
  • Flask module
  • Software
  • FL
  • llm
  • data science
  • testing
  • pathlib
  • oop
  • gui
  • visualization
  • audio edit
  • requests
  • stack
  • min heap
  • Linked List
  • machine learning
  • scripts
  • compare string
  • time delay
  • PythonZip
  • pandas dataframes
  • arange() method
  • SQLAlchemy
  • Activator
  • Music
  • AI
  • ML
  • import
  • file
  • jinja
  • pysimplegui
  • notebook
  • decouple
  • queue
  • heapify
  • Singly Linked List
  • intro
  • python scripts
  • learning python
  • python bugs
  • ZipFunction
  • plus equals
  • np.linspace
  • SQLAlchemy advance
  • Download
  • No
  • nlp
  • machiine learning
  • dask
  • file management
  • jinja2
  • ui
  • tdqm
  • configuration
  • deque
  • heap
  • Data Structure
  • howto
  • dict
  • csv in python
  • logging in python
  • Python Counter
  • python subprocess
  • numpy module
  • Python code generators
  • KMS
  • Office
  • modules
  • web scraping
  • scalable
  • pipx
  • templates
  • python not
  • pytesseract
  • env
  • push
  • search
  • Node
  • python tutorial
  • dictionary
  • csv file python
  • python logging
  • Counter class
  • Python assert
  • linspace
  • numbers_list
  • Tool
  • Key
  • automation
  • website data
  • autoscale
  • packages
  • snusbase
  • boolean
  • ocr
  • pyside6
  • pop
  • binary search
  • Insert Node
  • Python tips
  • python dictionary
  • Python's Built-in CSV Library
  • logging APIs
  • Constructing Counters
  • Assertions
  • Matplotlib Plotting
  • any() Function
  • Activation
  • Patch
  • threading
  • scrapy
  • game analysis
  • dependencies
  • security
  • not operation
  • pdf
  • build gui
  • dequeue
  • linear search
  • Add Node
  • Python tools
  • function
  • python update
  • logging module
  • Concatenate Data Frames
  • python comments
  • matplotlib
  • Recursion Limit
  • License
  • Pirated
  • square root
  • website extract python
  • steamspy
  • processing
  • cybersecurity
  • variable
  • image processing
  • incrementing
  • Data structures
  • algorithm
  • Print Node
  • installation
  • python function
  • pandas installation
  • Zen of Python
  • concatenation
  • Echo Client
  • Pygame
  • NumPy Pad()
  • Unlock
  • Bypass
  • pytorch
  • zipp
  • steam
  • multiprocessing
  • type hinting
  • global
  • argh
  • c vs python
  • Python
  • stacks
  • Sort
  • algorithms
  • install python
  • Scopes
  • how to install pandas
  • Philosophy of Programming
  • concat() function
  • Socket State
  • % Operator
  • Python YAML
  • Crack
  • Reddit
  • lightning
  • zip files
  • python reduce
  • library
  • dynamic
  • local
  • command line
  • define function
  • Pickle
  • enqueue
  • ascending
  • remove a node
  • Django
  • function scope
  • Tuple in Python
  • pandas groupby
  • pyenv
  • socket programming
  • Python Modulo
  • Dictionary Update()
  • Hack
  • sdk
  • python automation
  • main
  • reduce
  • typing
  • ord
  • print
  • network
  • matplotlib inline
  • Pickling
  • datastructure
  • bubble sort
  • find a node
  • Flask
  • calling function
  • tuple
  • GroupBy method
  • Pythonbrew
  • Np.Arange()
  • Modulo Operator
  • Python Or Operator
  • Keygen
  • cloud
  • pyautogui
  • python main
  • reduce function
  • type hints
  • python ord
  • format
  • python socket
  • jupyter
  • Python is a beautiful language.