我想为我所有与HTTP相关的测试都包含一个Web服务器。它不需要非常复杂。我宁愿不依赖在线。所以我可以测试一下我的程序的一些选项。
对此代码的任何提示都会有所帮助。我用BaseHTTPServer尝试了一些但尚未成功的东西。 nosetests命令似乎无限期地等待。
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
感谢您的帮助。
更新 - 2012:07:10T02:34:00Z
这是给定网站将返回CSS列表的代码。我想测试它是否返回正确的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)
然后在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
现在,代码依赖于在线服务器。它不应该。我希望能够添加大量不同类型的样式表组合并测试协议,然后在解析,组合等方面提供一些选项。
答案 0 :(得分:71)
启动Web服务器进行单元测试绝对不是一个好习惯。单元测试应该简单且隔离,这意味着它们应该避免执行IO操作,例如。
如果您要编写的内容实际上是单元测试,那么您应该制作自己的测试输入并查看mock objects。 Python是一种动态语言,模拟和猴子路径是编写单元测试的简单而强大的工具。特别要看一下优秀的Mock module。
因此,如果我们看一下您的CssTests
示例,那么您正在尝试测试css.getCssUriList
是否能够提取您提供的HTML片段中引用的所有CSS样式表。您在此特定单元测试中所做的不是测试您是否可以发送请求并从网站获得响应,对吧?您只是想确保给定一些HTML,您的函数会返回正确的CSS URL列表。因此,在此测试中,您显然不需要与真实的HTTP服务器通信。
我会做以下事情:
import unittest
class CssListTestCase(unittest.TestCase):
def setUp(self):
self.css = core.Css()
def test_css_list_should_return_css_url_list_from_html(self):
# Setup your test
sample_html = """
<html>
<head>
<title>Some web page</title>
<link rel='stylesheet' type='text/css' media='screen'
href='http://example.com/styles/full_url_style.css' />
<link rel='stylesheet' type='text/css' media='screen'
href='/styles/relative_url_style.css' />
</head>
<body><div>This is a div</div></body>
</html>
"""
base_url = "http://example.com/"
# Exercise your System Under Test (SUT)
css_urls = self.css.get_css_uri_list(sample_html, base_url)
# Verify the output
expected_urls = [
"http://example.com/styles/full_url_style.css",
"http://example.com/styles/relative_url_style.css"
]
self.assertListEqual(expected_urls, css_urls)
现在,不那么明显的是对getContent()
类的core.HttpRequests
方法进行单元测试。我想你使用的是HTTP库而不是在TCP套接字上发出自己的请求。
要将测试保持在单元级别,您不希望通过网络发送任何内容。您可以采取哪些措施来避免这种情况,即可通过测试确保您正确使用HTTP库。这不是测试代码的行为,而是测试它与周围其他对象的交互方式。
这样做的一种方法是明确依赖于该库:我们可以向HttpRequests.__init__
添加一个参数,以便将其传递给库的HTTP客户端实例。假设我使用提供HttpClient
对象的HTTP库,我们可以在其上调用get()
。你可以这样做:
class HttpRequests(object):
def __init__(self, http_client):
self.http_client = http_client
def get_content(self, url):
# You could imagine doing more complicated stuff here, like checking the
# response code, or wrapping your library exceptions or whatever
return self.http_client.get(url)
我们已经明确了依赖项,现在需要HttpRequests
的调用者满足需求:这称为依赖注入(DI)。
DI对两件事非常有用:
在这里,我们可以使用我们将提供给core.HttpRequests
的模拟对象,并且它将在不知不觉中使用,就像它是真正的库一样。之后,我们可以测试交互是否按预期进行。
import core
class HttpRequestsTestCase(unittest.TestCase):
def test_get_content_should_use_get_properly(self):
# Setup
url = "http://example.com"
# We create an object that is not a real HttpClient but that will have
# the same interface (see the `spec` argument). This mock object will
# also have some nice methods and attributes to help us test how it was used.
mock_http_client = Mock(spec=somehttplib.HttpClient)
# Exercise
http_requests = core.HttpRequests(mock_http_client)
content = http_requests.get_content(url)
# Here, the `http_client` attribute of `http_requests` is the mock object we
# have passed it, so the method that is called is `mock.get()`, and the call
# stops in the mock framework, without a real HTTP request being sent.
# Verify
# We expect our get_content method to have called our http library.
# Let's check!
mock_http_client.get.assert_called_with(url)
# We can find out what our mock object has returned when get() was
# called on it
expected_content = mock_http_client.get.return_value
# Since our get_content returns the same result without modification,
# we should have received it
self.assertEqual(content, expected_content)
我们现在测试了我们的get_content
方法与我们的HTTP库正确交互。我们已经定义了HttpRequests
对象的边界并对它们进行了测试,这是我们应该在单元测试级别上进行的。请求现在在该库的手中,并且我们的单元测试套件的作用当然不是测试库是否按预期工作。
现在想象我们决定使用伟大的requests library。它的API更具程序性,它不会提供我们可以从中获取HTTP请求的对象。相反,我们将导入模块并调用其get
方法。
我们HttpRequests
中的core.py
课程会看起来如下:
import requests
class HttpRequests(object):
# No more DI in __init__
def get_content(self, url):
# We simply delegate the HTTP work to the `requests` module
return requests.get(url)
不再有DI,所以现在,我们想知道:
requests
模块?在这里,您可以使用动态语言提供的另一种奇妙且有争议的机制:monkey patching。我们将在运行时用我们制作的对象替换requests
模块,并在我们的测试中使用。
我们的单元测试将看起来像:
import core
class HttpRequestsTestCase(unittest.TestCase):
def setUp(self):
# We create a mock to replace the `requests` module
self.mock_requests = Mock()
# We keep a reference to the current, real, module
self.old_requests = core.requests
# We replace the module with our mock
core.requests = self.mock_requests
def tearDown(self):
# It is very important that each unit test be isolated, so we need
# to be good citizen and clean up after ourselves. This means that
# we need to put back the correct `requests` module where it was
core.requests = self.old_requests
def test_get_content_should_use_get_properly(self):
# Setup
url = "http://example.com"
# Exercise
http_client = core.HttpRequests()
content = http_client.get_content(url)
# Verify
# We expect our get_content method to have called our http library.
# Let's check!
self.mock_requests.get.assert_called_with(url)
# We can find out what our mock object has returned when get() was
# called on it
expected_content = self.mock_requests.get.return_value
# Since our get_content returns the same result without modification,
# we should have received
self.assertEqual(content, expected_content)
为了简化这个过程,mock
模块有一个patch
装饰器来管理脚手架。然后我们只需要写:
import core
class HttpRequestsTestCase(unittest.TestCase):
@patch("core.requests")
def test_get_content_should_use_get_properly(self, mock_requests):
# Notice the extra param in the test. This is the instance of `Mock` that the
# decorator has substituted for us and it is populated automatically.
...
# The param is now the object we need to make our assertions against
expected_content = mock_requests.get.return_value
保持单元测试小巧,简单,快速和独立非常重要。依赖于另一台服务器运行的单元测试根本不是单元测试。为了解决这个问题,DI是一个很好的实践,模拟对象是一个很好的工具。
首先,要了解模拟的概念以及如何使用它们并不容易。像每个电动工具一样,它们也可以在你手中爆炸,例如让你相信你已经测试了一些东西,而实际上你没有。确保模拟对象的行为和输入/输出反映现实是至关重要的。
鉴于我们从未在单元测试级别与真正的HTTP服务器进行过交互,因此编写集成测试非常重要,这将确保我们的应用程序能够与现实生活中将要处理的服务器进行通信。我们可以通过专门为集成测试设置的完全成熟的服务器来实现这一目标,或者编写一个人为的。