web-dev-qa-db-ja.com

ResourceWarningのPython 3ユニットテストで閉じられていないソケット

_Python 2_と_Python 3_の間で互換性があるようにいくつかのコードを変更していますが、ユニットテストの出力に警告があります。

_/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/case.py:601:
    ResourceWarning: unclosed socket.socket fd=4,
    family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6,
    laddr=('1.1.2.3', 65087), raddr=('5.8.13.21', 8080)
_

requestsboto のような人気のあるライブラリーからもこれが起こっていると判断された小さな研究。

警告または filter it を完全に無視できます。私のサービスであれば、応答に_connection: close_ヘッダーを設定できます( link )。

_Python 3.6.1_の警告を示す例は次のとおりです。

app.py

_import requests

class Service(object):
    def __init__(self):
        self.session = requests.Session()

    def get_info(self):
        uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
        response = self.session.get(uri)
        if response.status_code == 200:
            return response.json()
        else:
            response.raise_for_status()

    def __del__(self):
        self.session.close()

if __name__ == '__main__':
    service = Service()
    print(service.get_info())
_

test.py

_import unittest

class TestService(unittest.TestCase):
    def test_growing(self):
        import app
        service = app.Service()
        res = service.get_info()
        self.assertTrue(res['items'][0]['new_active_users'] > 1)


if __name__ == '__main__':
    unittest.main()
_

セッションが明示的に閉じられ、__del__()に依存せずにこの種の警告が発生しないように、セッションを管理するより良い/正しい方法はありますか。

助けてくれてありがとう。

19
j12y

__del__にティアダウンロジックがあると、プログラムが不正確になったり、推論が難しくなる可能性があります。そのメソッドがいつ呼び出されるかは保証されないため、警告が表示される可能性があります。これに対処するには、いくつかの方法があります。

1)セッションを閉じるメソッドを公開し、テストで呼び出しますtearDown

unittestの-​​ tearDown メソッドを使用すると、各テストの後に実行されるコードを定義できます。このフックを使用してセッションを閉じると、テストが失敗した場合や例外が発生した場合でも機能します。

app.py

import requests

class Service(object):

    def __init__(self):
        self.session = requests.Session()

    def get_info(self):
        uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
        response = self.session.get(uri)
        if response.status_code == 200:
            return response.json()
        else:
            response.raise_for_status()

    def close(self):
        self.session.close()

if __name__ == '__main__':
    service = Service()
    print(service.get_info())
    service.close()

test.py

import unittest
import app

class TestService(unittest.TestCase):

    def setUp(self):
        self.service = app.Service()
        super().setUp()

    def tearDown(self):
        self.service.close()

    def test_growing(self):
        res = self.service.get_info()
        self.assertTrue(res['items'][0]['new_active_users'] > 1)

if __name__ == '__main__':
    unittest.main()

2)コンテキストマネージャーを使用する

コンテキストマネージャ は、何かのスコープを明示的に定義する非常に便利な方法でもあります。前の例では、すべての呼び出しサイトで.close()が正しく呼び出されるようにする必要があります。そうしないと、リソースがリークします。コンテキストマネージャを使用すると、コンテキストマネージャのスコープ内に例外がある場合でも、これは自動的に処理されます。

ソリューション1)の上に構築して、追加のマジックメソッド(__enter__および__exit__)を定義して、クラスがwithステートメントで動作するようにすることができます。

注:ここでの素晴らしい点は、このコードがソリューション1)の使用もサポートしていることです。明示的な.close()があり、何らかの理由でコンテキストマネージャーが不便だった場合に役立ちます。

app.py

import requests

class Service(object):

    def __init__(self):
        self.session = requests.Session()

    def __enter__(self):
        return self

    def get_info(self):
        uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
        response = self.session.get(uri)
        if response.status_code == 200:
            return response.json()
        else:
            response.raise_for_status()

    def close(self):
        self.session.close()

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

if __name__ == '__main__':
    with Service() as service:
        print(service.get_info())

test.py

import unittest

import app

class TestService(unittest.TestCase):

    def test_growing(self):
        with app.Service() as service:
            res = service.get_info()
        self.assertTrue(res['items'][0]['new_active_users'] > 1)

if __name__ == '__main__':
    unittest.main()

必要なものに応じて、setUp/tearDownとコンテキストマネージャーのいずれか、または組み合わせを使用して、その警告を取り除き、さらにコードでより明示的なリソース管理を行うことができます。

これは、警告をあまり気にしない場合に最適なソリューションです

warningsをインポートし、ドライバーが開始する場所にこの行を追加するだけです-

warnings.filterwarnings(action="ignore", message="unclosed", category=ResourceWarning)

2