[Python] come fare il mocking di urlopen

Pietro peter.zamb a gmail.com
Ven 18 Ago 2017 11:07:58 CEST


Ciao,

sto cercando di capire come fare il mock di urlopen, in modo da
testare il comportamento di una funzione, vorrei capire:

cosa sto sbagliando

come rendere il codice testabile sia su python3 che su python2

il codice è il seguente:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tempfile
import os

try:
    # Python 3.x
    from unittest import mock
    from urllib.request import urlopen
    from urllib.error import URLError
    URLOPEN_MOCKPATH = 'urllib.request.urlopen'
except ImportError:
    # Python 2.x
    import mock
    from urllib2 import urlopen
    from urllib2 import URLError
    URLOPEN_MOCKPATH = 'urllib2.urlopen'

import pytest
from pytest import raises

def myfunc(url, filename):
    try:
        chkret = urlopen(url)
    except URLError:
        chkret = ""
    if chkret:
        result = chkret.read()
        with open(filename, mode='w') as wfile:
            wfile.write(result)
    else:
        with open(filename, mode='r') as rfile:
            result = rfile.read()
    return result

@mock.patch(URLOPEN_MOCKPATH, autospec=True)
def test_myfunc(mock_urlopen):
    # create a temp file
    tmp_dir = tempfile.mkdtemp()
    filename = os.path.join(tmp_dir, 'mycache.txt')
    with open(filename, mode='w') as w:
        w.write("Success! (from file)")

    # ===========================================
    mock_chkret = mock.Mock()
    mock_chkret.read.return_value = "Success! (from urlopen)"
    mock_urlopen.return_value = mock_chkret

    result = myfunc("http://myulr.it", filename)

    mock_urlopen.assert_called()
    mock_chkret.read.assert_called()
    assert result == "Success! (from urlopen)"

    # ===========================================
    mock_urlopen.return_value = URLError

    result = myfunc("http://myulr.it", filename)

    mock_urlopen.assert_called()
    assert result == "Success! (from file)"

    # remove temporary file and directory
    os.rm(filename)
    os.rmdir(tmp_dir)

Quando eseguo i test ottengo:

$ pytest tests/test_urlopen_mock.py
======================= test session starts =================================
platform linux -- Python 3.6.2, pytest-3.2.1, py-1.4.34, pluggy-0.4.0
rootdir: /home/pietro/docdat/synapsees/src/synapsees-raspberry-git, inifile:
collected 1 item

tests/test_urlopen_mock.py F

======================= FAILURES ======================================
__________________________  test_myfunc
_________________________________________

mock_urlopen = <function urlopen at 0x7ff54690c0d0>

    @mock.patch(URLOPEN_MOCKPATH, autospec=True)
    def test_myfunc(mock_urlopen):
        # create a temp file
        tmp_dir = tempfile.mkdtemp()
        filename = os.path.join(tmp_dir, 'mycache.txt')
        with open(filename, mode='w') as w:
            w.write("Success! (from file)")

        # ===========================================
        mock_chkret = mock.Mock()
        mock_chkret.read.return_value = "Success! (from urlopen)"
        mock_urlopen.return_value = mock_chkret

        result = myfunc("http://myulr.it", filename)

>       mock_urlopen.assert_called()

tests/test_urlopen_mock.py:55:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/lib/python3.6/unittest/mock.py:197: in assert_called
    return mock.assert_called(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

_mock_self = <MagicMock name='urlopen' spec='function' id='140691438470928'>

    def assert_called(_mock_self):
        """assert that the mock was called at least once
            """
        self = _mock_self
        if self.call_count == 0:
            msg = ("Expected '%s' to have been called." %
                   self._mock_name or 'mock')
>           raise AssertionError(msg)
E           AssertionError: Expected 'urlopen' to have been called.

/usr/lib/python3.6/unittest/mock.py:786: AssertionError
==================== 1 failed in 0.13 seconds ===================

In sostanza sta ignorando il mio mock, e continua ad usare la funzione
urlopen, infatti se commento le righe:

mock_urlopen.assert_called()
mock_chkret.read.assert_called()

pytest fallisce perché restituisce il valore dal file e non da urlopen:

$ pytest tests/test_urlopen_mock.py
==================== test session starts ============================
platform linux -- Python 3.6.2, pytest-3.2.1, py-1.4.34, pluggy-0.4.0
rootdir: /home/pietro/docdat/synapsees/src/synapsees-raspberry-git, inifile:
collected 1 item

tests/test_urlopen_mock.py F

========================= FAILURES =============================

____________________________ test_myfunc ________________________________

mock_urlopen = <function urlopen at 0x7f958ce380d0>

    @mock.patch(URLOPEN_MOCKPATH, autospec=True)
    def test_myfunc(mock_urlopen):
        # create a temp file
        tmp_dir = tempfile.mkdtemp()
        filename = os.path.join(tmp_dir, 'mycache.txt')
        with open(filename, mode='w') as w:
            w.write("Success! (from file)")

        # ===========================================
        mock_chkret = mock.Mock()
        mock_chkret.read.return_value = "Success! (from urlopen)"
        mock_urlopen.return_value = mock_chkret

        result = myfunc("http://myulr.it", filename)

        #mock_urlopen.assert_called()
        #mock_chkret.read.assert_called()

>       assert result == "Success! (from urlopen)"
E       AssertionError: assert 'Success! (from file)' == 'Success!
(from urlopen)'
E         - Success! (from file)
E         ?                ^^
E         + Success! (from urlopen)
E         ?                ^^ ++ +

tests/test_urlopen_mock.py:58: AssertionError
================== 1 failed in 0.06 seconds ========================

Cosa sto sbagliando? come fare a far si che usi mock_urlopen?

Al momento mi interessa far funzionare i test solo su python3, ma non
sarebbe male riuscire a farli girare anche sotto python2, voi come
fate di solito?

Suggerimenti?

Grazie

Pietro


Maggiori informazioni sulla lista Python