I would like to include a Web server for all my test related to HTTP. It doesn't need to be very sophisticated. I would prefer not to be dependent of being online. So I could test some options of my program.
- Start the server
- Create a few resources (URI) with appropriate mime types, response code, etc.
- Run the tests (would be good to not have to start the server for each tests too)
- Shut down the server.
Any hints on this code would be helpful. I tried a few things with BaseHTTPServer but not successful yet. nosetests command seems to wait indefinitely.
import unittest
from foo import core
class HttpRequests(unittest.TestCase):
"""Tests for HTTP"""
def setUp(self):
"Starting a Web server"
self.port = 8080
# Here we need to start the server
#
# Then define a couple of URIs and their HTTP headers
# so we can test the code.
pass
def testRequestStyle(self):
"Check if we receive a text/css content-type"
myreq = core.httpCheck()
myuri = 'http://127.0.0.1/style/foo'
myua = "Foobar/1.1"
self.asserEqual(myreq.mimetype(myuri, myua), "text/css")
def testRequestLocation(self):
"another test"
pass
def tearDown(self):
"Shutting down the Web server"
# here we need to shut down the server
pass
thanks for any help.
Update - 2012:07:10T02:34:00Z
This is a code which for a given Web site will return the list of CSS. I want to test if it returns the right list of CSS.
import unittest
from foo import core
class CssTests(unittest.TestCase):
"""Tests for CSS requests"""
def setUp(self):
self.css = core.Css()
self.req = core.HttpRequests()
def testCssList(self):
"For a given Web site, check if we get the right list of linked stylesheets"
WebSiteUri = 'http://www.opera.com/'
cssUriList = [
'http://www.opera.com/css/handheld.css',
'http://www.opera.com/css/screen.css',
'http://www.opera.com/css/print.css',
'http://www.opera.com/css/pages/home.css']
content = self.req.getContent(WebSiteUri)
cssUriListReq = self.css.getCssUriList(content, WebSiteUri)
# we need to compare ordered list.
cssUriListReq.sort()
cssUriList.sort()
self.assertListEqual(cssUriListReq, cssUriList)
Then in foo/core.py
import urlparse
import requests
from lxml import etree
import cssutils
class Css:
"""Grabing All CSS for one given URI"""
def getCssUriList(self, htmltext, uri):
"""Given an htmltext, get the list of linked CSS"""
tree = etree.HTML(htmltext)
sheets = tree.xpath('//link[@rel="stylesheet"]/@href')
for i, sheet in enumerate(sheets):
cssurl = urlparse.urljoin(uri, sheet)
sheets[i] = cssurl
return sheets
Right now, the code depends on an online server. It should not. I want to be able to add plenty of different types of combination of stylesheets and to test the protocol and then later on some options on their parsing, combinations, etc.
Starting a web server for unit testing is definitely not a good practice. Unit tests should be simple and isolated, which means that they should avoid performing IO operations for example.
If what you want to write are really unit tests then you should craft your own test inputs and also look into mock objects. Python being a dynamic language, mocking and monkey pathing are easy and powerful tools for writing unit test. In particular, have a look at the excellent Mock module.
Simple unit test
So, if we have a look at your
CssTests
example, you are trying to test thatcss.getCssUriList
is able to extract all the CSS stylesheet referenced in a piece of HTML you give it. What you are doing in this particular unit test is not testing that you can send a request and get a response from a website, right? You simply want to make sure that given some HTML, your function returns the correct list of CSS URLs. So, in this test, you clearly do not need to talk to a real HTTP server.I would do something like the following:
Mocking with Dependency Injection
Now, something less obvious would be unit testing the
getContent()
method of yourcore.HttpRequests
class. I suppose you are using an HTTP library and not making your own requests on top of TCP sockets.To keep your tests at the unit level, you don't want to send anything over the wire. What you can do to avoid that, is having tests that ensure that you make use of your HTTP library correctly. This is about testing not the behaviour of your code but rather the way it interacts with the other objects around it.
One way to do so would be to make the dependency on that library explicit: we can add a parameter to the
HttpRequests.__init__
to pass it an instance of library's HTTP client. Say I use an HTTP library that provides aHttpClient
object on which we can callget()
. You could do something like:We have made the dependency explicit and the requirement now needs to be met by the caller of
HttpRequests
: this is called Dependency Injection (DI).DI is very useful for two things:
Here, we can use a mock object that we will give to
core.HttpRequests
and that it will use, unknowingly, as if it were the real library. After that, we can test that the interaction was conducted as expected.We have now tested that our
get_content
method interacts correctly with our HTTP library. We have defined the boundaries of ourHttpRequests
object and tested them, and this is as far as we should go at the unit test level. The request is now in the hand of that library and it is certainly not the role of our unit test suite to test that the library works as expected.Monkey patching
Now imagine that we decide to use the great requests library. Its API being more procedural, it does not present an object we can grab to make HTTP requests from. Instead, we would import the module and call its
get
method.Our
HttpRequests
class incore.py
would then look somethings like the following:No more DI, so now, we are left wondering:
requests
module properly?This is where you can use another fantastic, yet controversial, mechanism that dynamic languages offer: monkey patching. We will replace, at runtime, the
requests
module with an object we craft and can use in our test.Our unit test will then look something like:
To make this process less verbose, the
mock
module has apatch
decorator that looks after the scaffolding. We then only need to write:Conclusion
It is very important to keep unit test small, simple, fast, and self-contained. A unit test that relies on another server to be running is simply not a unit test. To help with that, DI is a great practice, and mock objects a great tool.
At first, it is not easy to get your head around the concept of mock and how to use them though. Like every power tool, they can also explode in your hands and for example make you believe you have tested something when in reality you have not. Making sure that the behaviour and input/output of mock objects reflects the reality is paramount.
P.S.
Given that we have never interacted with a real HTTP server at the unit test level, it is important to write Integration Tests that will make sure our application is able to talk to the sort of servers it will deal with in real life. We could do this with a fully fledged server set up specially for Integration Testing, or write a contrived one.