問題描述
我想為所有與 HTTP 相關的測試包括一個 Web 服務器.它不需要非常復雜.我寧愿不依賴于在線.所以我可以測試我的程序的一些選項.
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.
- 啟動服務器
- 使用適當的 mime 類型、響應代碼等創建一些資源 (URI).
- 運行測試(最好不必為每個測試啟動服務器)
- 關閉服務器.
有關此代碼的任何提示都會有所幫助.我用 BaseHTTPServer 嘗試了一些東西,但還沒有成功.nosetests 命令似乎無限期地等待.
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
感謝您的幫助.
更新 - 2012:07:10T02:34:00Z
這是一個代碼,對于給定的網站將返回 CSS 列表.我想測試它是否返回正確的 CSS 列表.
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)
然后在 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.
推薦答案
為單元測試啟動 Web 服務器絕對不是一個好習慣.單元測試應該簡單且隔離,這意味著它們應該避免執行例如 IO 操作.
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.
如果您想編寫真正的單元測試,那么您應該制作自己的測試輸入并查看模擬對象.Python 作為一種動態語言,模擬和猴子路徑是編寫單元測試的簡單而強大的工具.特別是,看看優秀的 Mock 模塊.
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.
因此,如果我們查看您的 CssTests
示例,您正在嘗試測試 css.getCssUriList
是否能夠提取片段中引用的所有 CSS 樣式表你給它的HTML.您在這個特定的單元測試中所做的并不是測試您可以發送請求并從網站獲得響應,對嗎?您只是想確保給定一些 HTML,您的函數返回正確的 CSS URL 列表.因此,在這個測試中,您顯然不需要與真正的 HTTP 服務器通信.
So, if we have a look at your CssTests
example, you are trying to test that css.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.
我會做如下的事情:
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'
/>
<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)
依賴注入模擬
現在,不太明顯的事情是對 core.HttpRequests
類的 getContent()
方法進行單元測試.我想您正在使用 HTTP 庫,而不是在 TCP 套接字上發出自己的請求.
Mocking with Dependency Injection
Now, something less obvious would be unit testing the getContent()
method of your core.HttpRequests
class. I suppose you are using an HTTP library and not making your own requests on top of TCP sockets.
為了使您的測試保持在 unit 級別,您不希望通過網絡發送任何內容.您可以做些什么來避免這種情況,即進行測試以確保您正確使用 HTTP 庫.這不是測試代碼的行為,而是測試它與周圍其他對象的交互方式.
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.
這樣做的一種方法是明確對該庫的依賴:我們可以向 HttpRequests.__init__
添加一個參數,以將一個庫的 HTTP 客戶端實例傳遞給它.假設我使用了一個提供 HttpClient
對象的 HTTP 庫,我們可以在該對象上調用 get()
.你可以這樣做:
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 a HttpClient
object on which we can call get()
. You could do something like:
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).
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 在兩件事上非常有用:
DI is very useful for two things:
- 它避免了您的代碼秘密依賴某個對象存在于某處的意外情況
- 它允許編寫測試,根據測試的目標注入不同類型的對象
在這里,我們可以使用我們將提供給 core.HttpRequests
的模擬對象,并且它會在不知不覺中使用它,就好像它是真正的庫一樣.之后,我們可以測試交互是否按預期進行.
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.
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
對象的邊界并對其進行了測試,這就是我們應該在單元測試級別進行的工作.該請求現在已掌握在該庫的手中,我們的單元測試套件當然不會負責測試該庫是否按預期工作.
We have now tested that our get_content
method interacts correctly with our HTTP library. We have defined the boundaries of our HttpRequests
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.
現在假設我們決定使用出色的requests 庫.它的 API 更加程序化,它沒有提供我們可以抓取來發出 HTTP 請求的對象.相反,我們會導入模塊并調用它的 get
方法.
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.
我們在 core.py
中的 HttpRequests
類將如下所示:
Our HttpRequests
class in core.py
would then look somethings like the following:
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,所以現在,我們想知道:
No more DI, so now, we are left wondering:
- 如何防止網絡交互發生?
- 如何測試我是否正確使用了
requests
模塊?
您可以在這里使用動態語言提供的另一種奇妙但有爭議的機制:猴子補丁.我們將在運行時將 requests
模塊替換為我們制作并可以在測試中使用的對象.
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:
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
裝飾器來處理腳手架.然后我們只需要寫:
To make this process less verbose, the mock
module has a patch
decorator that looks after the scaffolding. We then only need to write:
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 是一種很好的實踐,而模擬對象則是一種很好的工具.
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.
鑒于我們從未在單元測試級別與真正的 HTTP 服務器進行過交互,因此編寫集成測試以確保我們的應用程序能夠與它將在現實生活中處理的那種服務器進行通信非常重要.我們可以使用專門為集成測試設置的成熟服務器來做到這一點,或者編寫一個人為的服務器.
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.
這篇關于如何在 Python 的單元測試場景中模擬 HTTP 請求的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!