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
これは、特定のWebサイトに対して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
現在、コードはオンラインサーバーに依存しています。すべきではありません。スタイルシートのさまざまな種類の組み合わせをたくさん追加し、プロトコルをテストしてから、解析、組み合わせなどのオプションを後で実行できるようにしたい.
単体テストのためにWebサーバーを起動することは、間違いなく良い習慣ではありません。単体テストは単純で独立している必要があります。つまり、IO操作を実行しないようにする必要があります。
書きたいものが本当に単体テストである場合は、独自のテスト入力を作成し、 モックオブジェクト も調べる必要があります。 Pythonは動的言語であるため、モックとモンキーパスはユニットテストを書くための簡単で強力なツールです。特に、優れた モックモジュール をご覧ください。
したがって、CssTests
の例を見ると、_css.getCssUriList
_が、与えたHTMLで参照されているすべてのCSSスタイルシートを抽出できることをテストしようとしています。この特定の単体テストで行うことは、リクエストを送信し、Webサイトから応答を取得できることをテストすることではありませんか? 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)
_
さて、それほど明白ではないものは、_core.HttpRequests
_クラスのgetContent()
メソッドを単体テストすることです。 HTTPライブラリを使用しており、TCPソケットの上で独自の要求を行っていないと思います。
テストをunitレベルで維持するために、ネットワーク経由で何も送信したくありません。それを避けるためにできることは、HTTPライブラリを正しく使用することを確認するテストを行うことです。これは、コードの動作ではなく、コードが他のオブジェクトと相互作用する方法をテストすることに関するものです。
そのための1つの方法は、そのライブラリへの依存関係を明示的にすることです。パラメータを_HttpRequests.__init__
_に追加して、ライブラリのHTTPクライアントのインスタンスを渡すことができます。 get()
を呼び出すことができるHttpClient
オブジェクトを提供するHTTPライブラリを使用するとします。次のようなことができます:
_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は次の2つのことに非常に役立ちます。
ここでは、_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
メソッドを呼び出します。
_core.py
_のHttpRequests
クラスは、次のようになります。
_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サーバーとやり取りしたことがないことを考えると、アプリケーションが実際に処理する種類のサーバーと通信できることを確認する統合テストを作成することが重要です。統合テスト用に特別に設定された本格的なサーバーでこれを行うか、不自然なサーバーを作成します。