0
0

PythonでRequestsを使ったクラスのテストを書きたい

Posted at

動機

  • 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

プログラム

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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0