Jak wyśledzić żądanie HTTP w scenariuszu testów jednostkowych w Pythonie
Chciałbym dołączyć serwer WWW dla wszystkich moich testów związanych z HTTP. Nie musi być bardzo wyrafinowany. Wolałbym nie być uzależniony od bycia online. Więc mogę przetestować kilka opcji mojego programu.
- Uruchom serwer
- utwórz kilka zasobów (URI) z odpowiednimi typami mime, kodem odpowiedzi itp.
- Uruchom testy (dobrze byłoby nie uruchamiać serwera dla każdego testu)
- Wyłącz serwer.
Wszelkie wskazówki na ten temat kod byłby pomocny. Próbowałem kilku rzeczy z BaseHTTPServer, ale nie udało się jeszcze. polecenie nosetests wydaje się czekać w nieskończoność.
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
Dzięki za pomoc.
Aktualizacja-2012:07:10T02:34: 00Z
Jest to kod, który dla danej strony internetowej zwróci listę CSS. Chcę sprawdzić, czy zwraca właściwą listę 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)
Następnie w 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
W tej chwili kod zależy od serwera online. Nie powinno. Chcę móc aby dodać wiele różnych typów kombinacji arkuszy stylów i przetestować protokół, a następnie niektóre opcje dotyczące ich parsowania, kombinacji itp. 1 answers
Uruchamianie serwera www do testów jednostkowych zdecydowanie nie jest dobrą praktyką. Testy jednostkowe powinny być proste i izolowane, co oznacza, że powinny unikać wykonywania operacji IO na przykład.
Jeśli to, co chcesz napisać, to naprawdę testy jednostkowe, powinieneś stworzyć własne wejścia testowe, a także zajrzeć do mock objects. Python jako dynamiczny język, szyderczy i Małpi pathing są łatwymi i potężnymi narzędziami do pisania testów jednostkowych. W szczególności zajrzyj na doskonały Mock module .
Prosty test jednostkowy
Więc, jeśli przyjrzymy się twojemu przykładowi CssTests
, próbujesz sprawdzić, czy css.getCssUriList
jest w stanie wyodrębnić cały arkusz stylów CSS, do którego odwołujesz się w podanym przez Ciebie fragmencie HTML. To, co robisz w tym konkretnym teście jednostkowym, nie jest testem, że możesz wysłać zapytanie i uzyskać odpowiedź ze strony internetowej, prawda? Po prostu chcesz się upewnić, że biorąc pod uwagę kod HTML, twoja funkcja zwraca poprawną listę adresów URL CSS. Tak więc w tym teście, oczywiście nie musisz rozmawiać z prawdziwym serwerem HTTP.
Zrobiłbym coś takiego:
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)
Mocking with Dependency Injection
Teraz, czymś mniej oczywistym byłoby jednostkowe testowanie metodygetContent()
twojej klasy core.HttpRequests
. Przypuszczam, że używasz biblioteki HTTP i nie składasz własnych żądań na gniazdach TCP.
Aby utrzymać testy na poziomie jednostki , nie chcesz niczego wysyłać przez kabel. Co można zrobić, aby uniknąć to jest posiadanie testów, które zapewniają, że poprawnie korzystasz z biblioteki HTTP. Chodzi tu nie o testowanie zachowania kodu, ale raczej jego interakcji z innymi obiektami wokół niego.
Możemy dodać parametr doHttpRequests.__init__
, aby przekazać mu instancję klienta HTTP biblioteki. Powiedzmy, że używam biblioteki HTTP, która dostarcza HttpClient
obiekt, na którym możemy wywołać get()
. Mógłbyś coś zrobić. 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)
Uczyniliśmy zależność jawną i wymóg musi teraz zostać spełniony przez wywołującego HttpRequests
: nazywa się to iniekcją zależności (DI).
DI jest bardzo przydatne do dwóch rzeczy:
- unika niespodzianek, gdy twój kod potajemnie opiera się na jakimś obiekcie, który gdzieś istnieje Pozwala na pisanie testów, które wprowadzają różnego rodzaju obiekty w zależności od celu tego testu.]}
Tutaj możemy użyć makiety obiektu, który damy core.HttpRequests
i że będzie ona używać, nieświadomie, jak gdyby była prawdziwą biblioteką. Następnie możemy sprawdzić, czy interakcja została przeprowadzona zgodnie z oczekiwaniami.
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)
Przetestowaliśmy, czy nasza metoda get_content
poprawnie współdziała z naszą biblioteką HTTP. Zdefiniowaliśmy granice naszego obiektu HttpRequests
i przetestowaliśmy je, a to jest tak daleko, jak powinniśmy pójść na poziomie testu jednostkowego. Prośba jest teraz w rękach tej biblioteki i z pewnością nie jest to rola naszego zestawu testów jednostkowych aby sprawdzić, czy biblioteka działa zgodnie z oczekiwaniami.
Monkey patching
Teraz wyobraź sobie, że zdecydowaliśmy się użyć Wielkiej biblioteki zapytań . Jego API jest bardziej proceduralne, nie prezentuje obiektu, z którego możemy pobrać żądania HTTP. Zamiast tego zaimportowalibyśmy moduł i wywołalibyśmy jego metodę get
.
Nasza HttpRequests
klasa w core.py
wyglądałaby następująco:
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)
Koniec z DI, więc teraz pozostaje nam zastanawiać się:
- Jak czy mam zapobiegać interakcjom w sieci?
- Jak mam sprawdzić czy poprawnie używam modułu
requests
?
Tutaj możesz użyć innego fantastycznego, ale kontrowersyjnego mechanizmu, który oferuje języki dynamiczne: monkey patching . Zamienimy, w czasie wykonywania, moduł requests
na obiekt, który tworzymy i możemy użyć w naszym teście.
Nasz test jednostkowy będzie wyglądał następująco:
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)
Aby ten proces był mniej gadatliwy, moduł mock
posiada dekoratora, który zajmuje się rusztowaniem. Następnie wystarczy napisać:
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
Podsumowanie
Bardzo ważne jest, aby testy jednostkowe były małe, proste, szybkie i samodzielne. Test jednostkowy, który polega na uruchomieniu innego serwera, nie jest po prostu testem jednostkowym. Aby w tym pomóc, DI jest świetną praktyką, a mock obiektów świetnym narzędziem.
Na początku nie jest łatwo zrozumieć koncepcję mocka i jak z nich korzystać. Jak każde elektronarzędzie, mogą również eksplodować w twoich rękach i na przykład sprawić, że uwierzysz, że coś przetestowałeś, podczas gdy w rzeczywistości tego nie zrobiłeś. Najważniejsze jest upewnienie się, że zachowanie i Wejście/Wyjście obiektów wzorcowych odzwierciedla rzeczywistość.
P. S.
Biorąc pod uwagę, że nigdy nie wchodziliśmy w interakcję z prawdziwym serwerem HTTP na poziomie testów jednostkowych, ważne jest, aby pisać testy integracyjne, które upewnią naszą aplikację, że będzie w stanie rozmawiać z takimi serwerami, z którymi będzie miała do czynienia w prawdziwym życiu. Moglibyśmy zrób to z pełnoprawnym serwerem skonfigurowanym specjalnie do testowania integracji lub napisz wymyślony.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-11 15:45:29