動機
- Requestsでwebページを取得しBeautifulSoupで内容を取得するクラスのテストを行いたい
テストの手法
- pythonのhttp.serverを利用してサーバを起動
- 以下のコマンドを実行するとhttpサーバが立ち上がる
python -m http.server --bind 127.0.0.1 8000
- 各テストの前にテスト用ファイルを作成
- UseRequestsClassのメソッドを実行
- 作成したファイルにrequest.get
使用ライブラリ
- python 3.12.0
- pip 23.2.1
requirements.txt
requests==2.31.0
beautifulsoup4==4.12.2
pytest==8.0.0
プログラム
- GitHubリポジトリのリンク -py_requests_test
UseRequestsClass(テスト対象のクラス)
- urlからrequestsでwebページを取得
- execute(css_selector)では引数のcss_selectorのtextを取得
use_requests_class.py
from dataclasses import dataclass
import requests
from requests.exceptions import HTTPError, Timeout, RequestException
from bs4 import BeautifulSoup
@dataclass
class UseRequestsClass:
"""
HTTPリクエストを行い、指定されたCSSセレクタに基づいてHTML要素を取得するクラス。
Attributes:
url (str): リクエストを行う対象のURL
"""
url : str
def execute(self, css_selector : str) -> str | None:
"""
指定されたCSSセレクタに基づいてHTML要素を取得します。
Args:
css_selector (str): 取得するHTML要素を指定するCSSセレクタ
Returns:
str | None: 取得したHTML要素のテキスト。エラーが発生した場合はNone。
"""
try:
response = requests.get(self.url)
response.raise_for_status()
except HTTPError as http_err:
print(f'HTTPエラーが発生しました: {http_err}')
except Timeout as timeout_err:
print(f'タイムアウトエラーが発生しました: {timeout_err}')
except RequestException as req_err:
print(f'リクエストエラーが発生しました: {req_err}')
except Exception as err:
print(f'予期せぬエラーが発生しました: {err}')
else:
# エラーが発生しなかった場合の処理
soup = BeautifulSoup(response.content.decode('utf-8'), 'html.parser')
search_element_soup = soup.select_one(css_selector)
return search_element_soup.get_text("")
return None
tests
tests/test_use_requwsts_class.py
import pytest
from conftest import UseRequestsClass
import subprocess
import time
import socket
import urllib.parse
import os
SCRIPT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
HOST = '127.0.0.1'
PORT = 8000
CREATE_FILE_PATH_LIST = []
def get_request_base_url():
"""
リクエストのベースURLを取得します。
Returns:
str: ベースURL
"""
return f"http://{HOST}:{PORT}"
@pytest.fixture(scope="session")
def setup_server():
"""
テスト用のHTTPサーバーをセットアップします。
セッションスコープのフィクスチャとして使用されます。
"""
#サーバを起動
server_process = subprocess.Popen(['python', '-m', 'http.server', '--directory', SCRIPT_DIRECTORY,'--bind', HOST, str(PORT)])
wait_for_server()
yield
#サーバを終了
server_process.terminate()
server_process.wait()
#テストで作成したファイルを削除
for file_path in CREATE_FILE_PATH_LIST:
os.remove(file_path)
def wait_for_server():
"""
サーバが起動するまで待機する関数
"""
max_attempts = 30 # 最大で30回試行
wait_time = 1 # 待機時間(秒)
for _ in range(max_attempts):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.connect((HOST, PORT))
break
except (ConnectionRefusedError, OSError):
time.sleep(wait_time)
def create_file(file_name : str, file_content : str):
"""
指定されたファイル名と内容でファイルを作成し、作成したファイルパスをリストに追加します。
Args:
file_name (str): 作成するファイルの名前
file_content (str): ファイルに書き込む内容
"""
create_file_path = os.path.join(SCRIPT_DIRECTORY, file_name)
with open(create_file_path, 'w', encoding='utf-8') as file:
file.write(file_content)
CREATE_FILE_PATH_LIST.append(create_file_path)
def test_html(setup_server):
#正常なHTMLファイルに対して正しい結果が得られることを検証します。
file_name = "title.html"
file_content = r"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>テスト用のHTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>This is a simple HTML document for testing purposes.</p>
</body>
</html>
"""
create_file(file_name, file_content)
request_url = urllib.parse.urljoin(get_request_base_url(), file_name)
use_requests_class = UseRequestsClass(request_url)
assert use_requests_class.execute("title") == "テスト用のHTML"
assert use_requests_class.execute("body h1") == "Hello, World!"
assert use_requests_class.execute("body p") == "This is a simple HTML document for testing purposes."
def test_request_exception(setup_server):
#存在しないファイルへのリクエストに対してNoneが返ることを検証します。
request_url = urllib.parse.urljoin(get_request_base_url(), "non_existent_file")
use_requests_class = UseRequestsClass(request_url)
assert use_requests_class.execute("title") == None