# `unittest.mock` — ξεκινώντας

Added in version 3.3.

## Χρήση της Προσομοίωσης (Mock)

### Mock patching methods

Κοινές χρήσεις για αντικείμενα `Mock` περιλαμβάνουν:

- Επιδιόρθωση (Patching) μεθόδων

- Καταγραφή κλήσεων μεθόδων σε αντικείμενα

Ίσως θέλετε να αντικαταστήσετε μια μέθοδο σε ένα αντικείμενο για να ελέγξετε ότι καλείται με τα σωστά ορίσματα από ένα άλλο μέρος του συστήματος:

    >>> real = SomeClass()
    >>> real.method = MagicMock(name='method')
    >>> real.method(3, 4, 5, key='value')
    <MagicMock name='method()' id='...'>

Μόλις χρησιμοποιηθεί η προσομοίωσή μας (`real.method` σε αυτό το παράδειγμα) θα έχει μεθόδους και χαρακτηριστικά που σας επιτρέπουν να κάνετε δηλώσεις σχετικά με το πώς έχει χρησιμοποιηθεί.

Σημείωση

Στα περισσότερα από αυτά τα παραδείγματα οι κλάσεις `Mock` και `MagicMock` είναι εναλλάξιμες. Καθώς η `MagicMock` είναι η πιο ικανή κλάση, αποτελεί μια λογική επιλογή για χρήση από προεπιλογή.

Μόλις κληθεί η προσομοίωση, το χαρακτηριστικό `called` ορίζεται σε `True`. Πιο σημαντικό είναι ότι μπορούμε να χρησιμοποιήσουμε τη μέθοδο `assert_called_with()` ή `assert_called_once_with()` για να ελέγξουμε ότι κλήθηκε με τα σωστά ορίσματα.

Αυτό το παράδειγμα ελέγχει ότι η κλήση της `ProductionClass().method` έχει ως αποτέλεσμα μια κλήση στη μέθοδο `something`:

    >>> class ProductionClass:
    ...     def method(self):
    ...         self.something(1, 2, 3)
    ...     def something(self, a, b, c):
    ...         pass
    ...
    >>> real = ProductionClass()
    >>> real.something = MagicMock()
    >>> real.method()
    >>> real.something.assert_called_once_with(1, 2, 3)

### Mock for method calls on an object

Στο τελευταίο παράδειγμα επιδιορθώσαμε μια μέθοδο απευθείας σε ένα αντικείμενο για να ελέγξουμε ότι κλήθηκε σωστά. Μια άλλη κοινή χρήση είναι να περάσουμε ένα αντικείμενο σε μια μέθοδο (ή σε κάποιο μέρος του συστήματος υπό δοκιμή) και στη συνέχεια να ελέγξουμε ότι χρησιμοποιείται με τον σωστό τρόπο.

Η απλή `ProductionClass` παρακάτω έχει μια μέθοδο `closer`. Εάν κληθεί με ένα αντικείμενο, τότε καλεί τη μέθοδο `close` σε αυτό.

    >>> class ProductionClass:
    ...     def closer(self, something):
    ...         something.close()
    ...

Έτσι, για να το δοκιμάσουμε, πρέπει να περάσουμε ένα αντικείμενο με μια μέθοδο `close` και να ελέγξουμε ότι κλήθηκε σωστά.

    >>> real = ProductionClass()
    >>> mock = Mock()
    >>> real.closer(mock)
    >>> mock.close.assert_called_with()

Δεν χρειάζεται να κάνουμε καμία εργασία για να παρέχουμε τη μέθοδο “close” στην προσομοίωσή μας. Η πρόσβαση στο close τη δημιουργεί. Έτσι, αν το “close” δεν έχει ήδη κληθεί, τότε η πρόσβαση σε αυτό στη δοκιμή θα το δημιουργήσει, αλλά η `assert_called_with()` θα κάνει raise μια εξαίρεση αποτυχίας.

### Mocking classes

Μια συνηθισμένη περίπτωση χρήσης είναι η προσομοίωση κλάσεων που δημιουργούνται από τον κώδικα υπό δοκιμή. Όταν επιδιορθώνετε (patch) μια κλάση, τότε αυτή η κλάση αντικαθίσταται με μια προσομοίωση. Τα στιγμιότυπα δημιουργούνται με την κλήση της κλάσης. Αυτό σημαίνει ότι έχετε πρόσβαση στο «στιγμιότυπο προσομοίωσης» εξετάζοντας την τιμή επιστροφής της προσομοιωμένης κλάσης.

Στο παρακάτω παράδειγμα έχουμε μια συνάρτηση `some_function` που δημιουργεί ένα στιγμιότυπο της `Foo` και καλεί μια μέθοδο σε αυτό. Η κλήση στη `patch()` αντικαθιστά την κλάση `Foo` με μια προσομοίωση. Το στιγμιότυπο της `Foo` είναι το αποτέλεσμα της κλήσης της προσομοίωσης, οπότε διαμορφώνεται τροποποιώντας το χαρακτηριστικό `return_value` της προσομοίωσης.

    >>> def some_function():
    ...     instance = module.Foo()
    ...     return instance.method()
    ...
    >>> with patch('module.Foo') as mock:
    ...     instance = mock.return_value
    ...     instance.method.return_value = 'the result'
    ...     result = some_function()
    ...     assert result == 'the result'

### Ονομασία των προσομοιώσεών σας

Μπορεί να είναι χρήσιμο να δώσετε στις προσομοιώσεις σας ένα όνομα. Το όνομα εμφανίζεται στην αναπαράσταση (repr) της προσομοίωσης και μπορεί να είναι χρήσιμο όταν η προσομοίωση εμφανίζεται σε μηνύματα αποτυχίας δοκιμών. Το όνομα επίσης μεταδίδεται σε χαρακτηριστικά ή μεθόδους της προσομοίωσης:

    >>> mock = MagicMock(name='foo')
    >>> mock
    <MagicMock name='foo' id='...'>
    >>> mock.method
    <MagicMock name='foo.method' id='...'>

### Tracking all calls

Συχνά θέλετε να παρακολουθείτε περισσότερες από μία κλήσεις σε μια μέθοδο. Το χαρακτηριστικό `mock_calls` καταγράφει όλες τις κλήσεις σε θυγατρικά χαρακτηριστικά της προσομοίωσης - και επίσης στα παιδιά τους.

    >>> mock = MagicMock()
    >>> mock.method()
    <MagicMock name='mock.method()' id='...'>
    >>> mock.attribute.method(10, x=53)
    <MagicMock name='mock.attribute.method()' id='...'>
    >>> mock.mock_calls
    [call.method(), call.attribute.method(10, x=53)]

Εάν κάνετε μια δήλωση σχετικά με το `mock_calls` και έχουν κληθεί απροσδόκητες μέθοδοι, τότε η δήλωση θα αποτύχει. Αυτό είναι χρήσιμο επειδή εκτός από το να δηλώνετε ότι οι κλήσεις που περιμένατε έχουν γίνει, ελέγχετε επίσης ότι έγιναν με τη σωστή σειρά και χωρίς επιπλέον κλήσεις:

Χρησιμοποιήστε το αντικείμενο `call` για να κατασκευάσετε λίστες για σύγκριση με το `mock_calls`:

    >>> expected = [call.method(), call.attribute.method(10, x=53)]
    >>> mock.mock_calls == expected
    True

Ωστόσο, οι παράμετροι σε κλήσεις που επιστρέφουν προσομοιώσεις δεν καταγράφονται, που σημαίνει ότι δεν είναι δυνατή η παρακολούθηση εμφωλευμένων κλήσεων όπου οι παράμετροι που χρησιμοποιούνται για τη δημιουργία προγόνων είναι σημαντικοί:

    >>> m = Mock()
    >>> m.factory(important=True).deliver()
    <Mock name='mock.factory().deliver()' id='...'>
    >>> m.mock_calls[-1] == call.factory(important=False).deliver()
    True

### Setting return values and attributes

Ορισμός των τιμών επιστροφής σε ένα αντικείμενο προσομοίωσης είναι εξαιρετικά εύκολος:

    >>> mock = Mock()
    >>> mock.return_value = 3
    >>> mock()
    3

Φυσικά μπορείτε να κάνετε το ίδιο για μεθόδους στην προσομοίωση:

    >>> mock = Mock()
    >>> mock.method.return_value = 3
    >>> mock.method()
    3

Η τιμή επιστροφής μπορεί επίσης να οριστεί στον κατασκευαστή:

    >>> mock = Mock(return_value=3)
    >>> mock()
    3

Εάν χρειάζεστε έναν ορισμό χαρακτηριστικού στην προσομοίωσή σας, απλώς κάντε το:

    >>> mock = Mock()
    >>> mock.x = 3
    >>> mock.x
    3

Μερικές φορές θέλετε να προσομοιώσετε μια πιο σύνθετη κατάσταση, όπως για παράδειγμα `mock.connection.cursor().execute("SELECT 1")`. Εάν θέλαμε αυτή τη κλήση να επιστρέψει μια λίστα, τότε πρέπει να διαμορφώσουμε το αποτέλεσμα της εμφωλευμένης κλήσης.

Μπορούμε να χρησιμοποιήσουμε το αντικείμενο `call` για να κατασκευάσουμε την λίστα των κλήσεων σε μια «αλυσιδωτή κλήση» όπως αυτή για εύκολη δήλωση αργότερα:

    >>> mock = Mock()
    >>> cursor = mock.connection.cursor.return_value
    >>> cursor.execute.return_value = ['foo']
    >>> mock.connection.cursor().execute("SELECT 1")
    ['foo']
    >>> expected = call.connection.cursor().execute("SELECT 1").call_list()
    >>> mock.mock_calls
    [call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]
    >>> mock.mock_calls == expected
    True

Είναι η κλήση στο `.call_list()` που μετατρέπει το αντικείμενο κλήσης σε μια λίστα κλήσεων που αντιπροσωπεύουν τις αλυσιδωτές κλήσεις.

### Πρόκληση εξαιρέσεων με προσομοιώσεις

Ένα χρήσιμο χαρακτηριστικό είναι το `side_effect`. Εάν το ορίσετε σε μια κλάση εξαίρεσης ή ένα στιγμιότυπο, τότε η εξαίρεση θα γίνει raise όταν κληθεί η προσομοίωση.

    >>> mock = Mock(side_effect=Exception('Boom!'))
    >>> mock()
    Traceback (most recent call last):
      ...
    Exception: Boom!

### Συναρτήσεις παρενέργειας και iterables

Το `side_effect` μπορεί επίσης να οριστεί σε μια συνάρτηση ή ένα iterable. Η χρήση του `side_effect` ως iterable είναι όταν η προσομοίωσή σας πρόκειται να κληθεί αρκετές φορές, και θέλετε κάθε κλήση να επιστρέφει μια διαφορετική τιμή. Όταν ορίζετε το `side_effect` σε ένα iterable, κάθε κλήση στην προσομοίωση επιστρέφει την επόμενη τιμή από το iterable:

    >>> mock = MagicMock(side_effect=[4, 5, 6])
    >>> mock()
    4
    >>> mock()
    5
    >>> mock()
    6

Για πιο προηγμένες χρήσεις, όπως η δυναμική μεταβολή των τιμών επιστροφής ανάλογα με το πώς καλείται η προσομοίωση, το `side_effect` μπορεί να είναι μια συνάρτηση. Η συνάρτηση θα κληθεί με τα ίδια ορίσματα με την προσομοίωση. Ό,τι επιστρέφει η συνάρτηση είναι αυτό που επιστρέφει η κλήση:

    >>> vals = {(1, 2): 1, (2, 3): 2}
    >>> def side_effect(*args):
    ...     return vals[args]
    ...
    >>> mock = MagicMock(side_effect=side_effect)
    >>> mock(1, 2)
    1
    >>> mock(2, 3)
    2

### Προσομοίωση ασύγχρονων επαναληπτών

Από την έκδοση Python 3.8, οι `AsyncMock` και `MagicMock` έχουν υποστήριξη για την προσομοίωση ασύγχρονων επαναληπτών μέσω του `__aiter__`. Το χαρακτηριστικό `return_value` του `__aiter__` μπορεί να χρησιμοποιηθεί για να ορίσει τις τιμές επιστροφής που θα χρησιμοποιηθούν για την επανάληψη.

    >>> mock = MagicMock()  # AsyncMock also works here
    >>> mock.__aiter__.return_value = [1, 2, 3]
    >>> async def main():
    ...     return [i async for i in mock]
    ...
    >>> asyncio.run(main())
    [1, 2, 3]

### Προσομοίωση ασύγχρονου διαχειριστή περιεχομένου

Από την έκδοση Python 3.8, οι `AsyncMock` και `MagicMock` έχουν υποστήριξη για την προσομοίωση ασύγχρονων διαχειριστών μέσω των `__aenter__` και `__aexit__`. Από προεπιλογή, οι `__aenter__` και `__aexit__` είναι στιγμιότυπα `AsyncMock` που επιστρέφουν μια ασύγχρονη συνάρτηση.

    >>> class AsyncContextManager:
    ...     async def __aenter__(self):
    ...         return self
    ...     async def __aexit__(self, exc_type, exc, tb):
    ...         pass
    ...
    >>> mock_instance = MagicMock(AsyncContextManager())  # AsyncMock also works here
    >>> async def main():
    ...     async with mock_instance as result:
    ...         pass
    ...
    >>> asyncio.run(main())
    >>> mock_instance.__aenter__.assert_awaited_once()
    >>> mock_instance.__aexit__.assert_awaited_once()

### Creating a mock from an existing object

Ένα πρόβλημα με την υπερβολική χρήση της προσομοίωσης είναι ότι συνδέει τις δοκιμές σας με την υλοποίηση των προσομοιώσεών σας αντί με τον πραγματικό κώδικα. Υποθέστε ότι έχετε μια κλάση που υλοποιεί την `some_method`. Σε μια δοκιμή για μια άλλη κλάση, παρέχετε μια προσομοίωση αυτού του αντικειμένου που επίσης παρέχει την `some_method`. Εάν αργότερα αναδιαρθρώσετε την πρώτη κλάση, έτσι ώστε να μην έχει πλέον την `some_method` - τότε οι δοκιμές σας θα συνεχίσουν να περνούν, παρόλο που ο κώδικάς σας είναι τώρα σπασμένος!

`Mock` σας επιτρέπει να παρέχετε ένα αντικείμενο ως προδιαγραφή για την προσομοίωση, χρησιμοποιώντας το όρισμα λέξης-κλειδιού spec. Η πρόσβαση σε μεθόδους / χαρακτηριστικά στην προσομοίωση που δεν υπάρχουν στο αντικείμενο προδιαγραφής σας θα γίνει raise αμέσως ένα σφάλμα χαρακτηριστικού. Εάν αλλάξετε την υλοποίηση της προδιαγραφής σας, τότε οι δοκιμές που χρησιμοποιούν αυτή την κλάση θα αρχίσουν να αποτυγχάνουν αμέσως χωρίς να χρειάζεται να δημιουργήσετε ένα στιγμιότυπο της κλάσης σε αυτές τις δοκιμές.

    >>> mock = Mock(spec=SomeClass)
    >>> mock.old_method()
    Traceback (most recent call last):
       ...
    AttributeError: Mock object has no attribute 'old_method'. Did you mean: 'class_method'?

Η χρήση μιας προδιαγραφής επιτρέπει επίσης μια πιο έξυπνη αντιστοίχιση των κλήσεων που γίνονται στην προσομοίωση, ανεξάρτητα από το αν ορισμένες παράμετροι περάστηκαν ως ορισμένες ή ονομαστικές παραμέτρους:

    >>> def f(a, b, c): pass
    ...
    >>> mock = Mock(spec=f)
    >>> mock(1, 2, 3)
    <Mock name='mock()' id='140161580456576'>
    >>> mock.assert_called_with(a=1, b=2, c=3)

Εάν θέλετε αυτή η πιο έξυπνη αντιστοίχιση να λειτουργεί επίσης με κλήσεις μεθόδων στην προσομοίωση, μπορείτε να χρησιμοποιήσετε auto-speccing.

Εάν θέλετε μια πιο ισχυρή μορφή προδιαγραφής που αποτρέπει τον ορισμό αυθαίρετων χαρακτηριστικών καθώς και την πρόσβαση σε αυτά, τότε μπορείτε να χρησιμοποιήσετε το spec_set αντί για το spec.

### Χρήση του side_effect για επιστροφή περιεχομένου ανά αρχείο

Η `mock_open()` χρησιμοποιείται για την επιδιόρθωση (patch) της `open()` μεθόδου. Το `side_effect` μπορεί να χρησιμοποιηθεί για την επιστροφή ενός νέου αντικειμένου Mock ανά κλήση. Αυτό μπορεί να χρησιμοποιηθεί για την επιστροφή διαφορετικών περιεχομένων ανά αρχείο που αποθηκεύονται σε ένα λεξικό:

    DEFAULT = "default"
    data_dict = {"file1": "data1",
                 "file2": "data2"}

    def open_side_effect(name):
        return mock_open(read_data=data_dict.get(name, DEFAULT))()

    with patch("builtins.open", side_effect=open_side_effect):
        with open("file1") as file1:
            assert file1.read() == "data1"

        with open("file2") as file2:
            assert file2.read() == "data2"

        with open("file3") as file2:
            assert file2.read() == "default"

## Patch decorators

Σημείωση

Με την `patch()` έχει σημασία να επιδιορθώνετε αντικείμενα στο χώρο ονομάτων όπου αναζητούνται. Αυτό είναι συνήθως απλό, αλλά για έναν γρήγορο οδηγό διαβάστε where to patch.

Μια κοινή ανάγκη στις δοκιμές είναι να επιδιορθώσετε ένα χαρακτηριστικό κλάσης ή ένα χαρακτηριστικό μονάδας, για παράδειγμα επιδιόρθωση ενός ενσωματωμένου ή επιδιόρθωση μιας κλάσης σε μια μονάδα για να ελέγξετε ότι δημιουργείται. Οι μονάδες και οι κλάσεις είναι ουσιαστικά παγκόσμιες, οπότε η επιδιόρθωση σε αυτές πρέπει να αναιρεθεί μετά τη δοκιμή ή η επιδιόρθωση θα επιμείνει σε άλλες δοκιμές και θα προκαλέσει προβλήματα που είναι δύσκολα να διαγνωστούν.

Η προσομοίωση παρέχει τρεις βολικούς διακοσμητές για αυτό: `patch()`, `patch.object()` και `patch.dict()`. Η `patch` παίρνει μια μοναδική συμβολοσειρά, της μορφής `package.module.Class.attribute` για να καθορίσει το χαρακτηριστικό που επιδιορθώνετε. Επίσης παίρνει προαιρετικά μια τιμή με την οποία θέλετε να αντικατασταθεί το χαρακτηριστικό (ή η κλάση ή οτιδήποτε). “patch.object” παίρνει ένα αντικείμενο και το όνομα του χαρακτηριστικού που θα θέλατε να επιδιορθώσετε, συν προαιρετικά την τιμή με την οποία θα το επιδιορθώσετε.

`patch.object`:

    >>> original = SomeClass.attribute
    >>> @patch.object(SomeClass, 'attribute', sentinel.attribute)
    ... def test():
    ...     assert SomeClass.attribute == sentinel.attribute
    ...
    >>> test()
    >>> assert SomeClass.attribute == original

    >>> @patch('package.module.attribute', sentinel.attribute)
    ... def test():
    ...     from package.module import attribute
    ...     assert attribute is sentinel.attribute
    ...
    >>> test()

Εάν επιδιορθώνετε ένα module (συμπεριλαμβανομένου του `builtins`), τότε χρησιμοποιήστε την `patch()` αντί για την `patch.object()`:

    >>> mock = MagicMock(return_value=sentinel.file_handle)
    >>> with patch('builtins.open', mock):
    ...     handle = open('filename', 'r')
    ...
    >>> mock.assert_called_with('filename', 'r')
    >>> assert handle == sentinel.file_handle, "incorrect file handle returned"

Το όνομα του module μπορεί να είναι “τελεία”, με τη μορφή `package.module` εάν χρειάζεται:

    >>> @patch('package.module.ClassName.attribute', sentinel.attribute)
    ... def test():
    ...     from package.module import ClassName
    ...     assert ClassName.attribute == sentinel.attribute
    ...
    >>> test()

Ένα ωραίο μοτίβο είναι να διακοσμείτε πραγματικά τις μεθόδους δοκιμής:

    >>> class MyTest(unittest.TestCase):
    ...     @patch.object(SomeClass, 'attribute', sentinel.attribute)
    ...     def test_something(self):
    ...         self.assertEqual(SomeClass.attribute, sentinel.attribute)
    ...
    >>> original = SomeClass.attribute
    >>> MyTest('test_something').test_something()
    >>> assert SomeClass.attribute == original

Εάν θέλετε να επιδιορθώσετε με μια προσομοίωση, μπορείτε να χρησιμοποιήσετε `patch()` με μόνο ένα όρισμα (ή `patch.object()` με δύο ορίσματα). Η προσομοίωση θα δημιουργηθεί για εσάς και θα περαστεί στη συνάρτηση / μέθοδο δοκιμής:

    >>> class MyTest(unittest.TestCase):
    ...     @patch.object(SomeClass, 'static_method')
    ...     def test_something(self, mock_method):
    ...         SomeClass.static_method()
    ...         mock_method.assert_called_with()
    ...
    >>> MyTest('test_something').test_something()

Μπορείτε να στοιβάξετε πολλαπλούς διακοσμητές επιδιόρθωσης χρησιμοποιώντας

    >>> class MyTest(unittest.TestCase):
    ...     @patch('package.module.ClassName1')
    ...     @patch('package.module.ClassName2')
    ...     def test_something(self, MockClass2, MockClass1):
    ...         self.assertIs(package.module.ClassName1, MockClass1)
    ...         self.assertIs(package.module.ClassName2, MockClass2)
    ...
    >>> MyTest('test_something').test_something()

Όταν εμφωλεύετε διακοσμητές επιδιόρθωσης, οι προσομοιώσεις περνιούνται στη διακοσμημένη συνάρτηση με την ίδια σειρά που εφαρμόστηκαν (η κανονική τάξη Python με την οποία εφαρμόζονται οι διακοσμητές). Αυτό σημαίνει από κάτω προς τα πάνω, οπότε στο παραπάνω παράδειγμα η προσομοίωση για το `test_module.ClassName2` περνάει πρώτη.

Υπάρχει επίσης η `patch.dict()` για τον ορισμό τιμών σε ένα λεξικό μόνο κατά τη διάρκεια ενός πεδίου και την αποκατάσταση του λεξικού στην αρχική του κατάσταση όταν τελειώνει η δοκιμή:

    >>> foo = {'key': 'value'}
    >>> original = foo.copy()
    >>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
    ...     assert foo == {'newkey': 'newvalue'}
    ...
    >>> assert foo == original

Η `patch`, η `patch.object` και η `patch.dict` μπορούν όλες να χρησιμοποιηθούν ως διαχειριστές συμφραζομένων.

Όπου χρησιμοποιείτε την `patch()` για να δημιουργήσετε μια προσομοίωση για εσάς, μπορείτε να λάβετε μια αναφορά στην προσομοίωση χρησιμοποιώντας τη μορφή «as» της δήλωσης with:

    >>> class ProductionClass:
    ...     def method(self):
    ...         pass
    ...
    >>> with patch.object(ProductionClass, 'method') as mock_method:
    ...     mock_method.return_value = None
    ...     real = ProductionClass()
    ...     real.method(1, 2, 3)
    ...
    >>> mock_method.assert_called_with(1, 2, 3)

Ως εναλλακτική, η `patch`, η `patch.object` και η `patch.dict` μπορούν να χρησιμοποιηθούν ως διακοσμητές κλάσης. Όταν χρησιμοποιούνται με αυτόν τον τρόπο, είναι το ίδιο με την εφαρμογή του διακοσμητή ξεχωριστά σε κάθε μέθοδο του οποίου το όνομα ξεκινά με «test».

## Further examples

Εδώ είναι μερικά ακόμη παραδείγματα για μερικά ελαφρώς πιο προηγμένα σενάρια.

### Προσομοίωση αλυσιδωτών κλήσεων

Η προσομοίωση αλυσιδωτών κλήσεων είναι στην πραγματικότητα απλή με την προσομοίωση μόλις κατανοήσετε το χαρακτηριστικό `return_value`. Όταν καλείται μια προσομοίωση για πρώτη φορά, ή λαμβάνετε την `return_value` της πριν καλεστεί, δημιουργείται μια νέα `Mock`.

Αυτό σημαίνει ότι μπορείτε να δείτε πώς έχει χρησιμοποιηθεί το αντικείμενο που επιστρέφεται από μια κλήση σε ένα προσομοιωμένο αντικείμενο, εξετάζοντας την προσομοίωση `return_value`:

    >>> mock = Mock()
    >>> mock().foo(a=2, b=3)
    <Mock name='mock().foo()' id='...'>
    >>> mock.return_value.foo.assert_called_with(a=2, b=3)

Από εδώ είναι ένα απλό βήμα για τη διαμόρφωση και στη συνέχεια τη δημιουργία δηλώσεων σχετικά με τις αλυσιδωτές κλήσεις. Φυσικά μια άλλη εναλλακτική είναι να γράψετε τον κώδικά σας με πιο δοκιμαστικό τρόπο από την αρχή…

Λοιπόν, υποθέτουμε ότι έχουμε κάποιο κώδικα που μοιάζει λίγο με αυτό:

    >>> class Something:
    ...     def __init__(self):
    ...         self.backend = BackendProvider()
    ...     def method(self):
    ...         response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
    ...         # more code

Υποθέτοντας ότι το `BackendProvider` έχει ήδη δοκιμαστεί καλά, πώς δοκιμάζουμε τη `method()`; Συγκεκριμένα, θέλουμε να δοκιμάσουμε ότι η ενότητα κώδικα `# more code` χρησιμοποιεί το αντικείμενο απόκρισης με τον σωστό τρόπο.

Καθώς αυτή η αλυσίδα κλήσεων γίνεται από ένα χαρακτηριστικό στιγμιότυπου, μπορούμε να επιδιορθώσουμε (monkey patch) το χαρακτηριστικό `backend` σε ένα στιγμιότυπο `Something`. Σε αυτή την συγκεκριμένη περίπτωση, μας ενδιαφέρει μόνο η τιμή επιστροφής από την τελική κλήση στο `start_call`, οπότε δεν έχουμε πολλή διαμόρφωση να κάνουμε. Ας υποθέσουμε ότι το αντικείμενο που επιστρέφει είναι “σαν αρχείο”, οπότε θα διασφαλίσουμε ότι το αντικείμενο απόκρισης μας χρησιμοποιεί την ενσωματωμένη `open()` ως το `spec` της.

Για να το κάνουμε αυτό, δημιουργούμε ένα στιγμιότυπο προσομοίωσης ως το mock backend μας και δημιουργούμε ένα αντικείμενο προσομοίωσης απόκρισης για αυτό. Για να ορίσουμε την απόκριση ως την τιμή επιστροφής για εκείνο το τελικό `start_call`, θα μπορούσαμε να κάνουμε αυτό:

    mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response

Μπορούμε να το κάνουμε με έναν ελαφρώς πιο ωραίο τρόπο χρησιμοποιώντας τη `configure_mock()` μέθοδο για να ορίσουμε απευθείας την τιμή επιστροφής για εμάς:

    >>> something = Something()
    >>> mock_response = Mock(spec=open)
    >>> mock_backend = Mock()
    >>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}
    >>> mock_backend.configure_mock(**config)

Με αυτά, επιδιορθώνουμε (monkey patch) το «mock backend» στη θέση του και μπορούμε να κάνουμε την πραγματική κλήση:

    >>> something.backend = mock_backend
    >>> something.method()

Με το χαρακτηριστικό `mock_calls` μπορούμε να ελέγξουμε την αλυσιδωτή κλήση με μια μοναδική δήλωση. Μια αλυσιδωτή κλήση είναι πολλές κλήσεις σε μία γραμμή κώδικα, οπότε θα υπάρχουν πολλές εισαγωγές στο `mock_calls`. Μπορούμε να χρησιμοποιήσουμε τη `call.call_list()` για να δημιουργήσουμε αυτή τη λίστα κλήσεων για εμάς:

    >>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
    >>> call_list = chained.call_list()
    >>> assert mock_backend.mock_calls == call_list

### Μερική προσομοίωση

Για ορισμένες δοκιμές, μπορεί να θέλετε να προσομοιώσετε μια κλήση στη `datetime.date.today()` για να επιστρέψετε μια γνωστή ημερομηνία, αλλά δεν θέλετε να αποτρέψετε τον κώδικα υπό δοκιμή από τη δημιουργία νέων αντικειμένων ημερομηνίας. Δυστυχώς, η `datetime.date` είναι γραμμένη σε C, οπότε δεν μπορείτε απλώς να κάνετε monkey-patch στη στατική `datetime.date.today()` μέθοδο.

Αντίθετα, μπορείτε ουσιαστικά να τυλίξετε την κλάση ημερομηνίας με μια προσομοίωση, ενώ περνάτε κλήσεις στον κατασκευαστή στην πραγματική κλάση (και επιστρέφοντας πραγματικά στιγμιότυπα).

Η `patch decorator` χρησιμοποιείται εδώ για να επιδιορθώσετε την κλάση ημερομηνίας στο μοντέλο υπό δοκιμή. Το χαρακτηριστικό `side_effect` στην προσομοίωση της κλάσης ημερομηνίας ρυθμίζεται στη συνέχεια σε μια συνάρτηση λάμδα που επιστρέφει μια πραγματική ημερομηνία. Όταν καλείται η προσομοίωση της κλάσης ημερομηνίας, θα κατασκευαστεί και θα επιστραφεί μια πραγματική ημερομηνία από το `side_effect`.

    >>> import datetime as dt
    >>> with patch('mymodule.date') as mock_date:
    ...     mock_date.today.return_value = dt.date(2010, 10, 8)
    ...     mock_date.side_effect = lambda *args, **kw: dt.date(*args, **kw)
    ...
    ...     assert mymodule.date.today() == dt.date(2010, 10, 8)
    ...     assert mymodule.date(2009, 6, 8) == dt.date(2009, 6, 8)

Σημειώστε ότι δεν επιδιορθώνουμε παγκοσμίως την `datetime.date`, αλλά επιδιορθώνουμε την `date` στο μοντέλο που τη χρησιμοποιεί. Δείτε where to patch.

Όταν καλείται το `date.today()`, επιστρέφεται μια γνωστή ημερομηνία, αλλά οι κλήσεις στον κατασκευαστή `date(...)` εξακολουθούν να επιστρέφουν κανονικές ημερομηνίες. Χωρίς αυτό, μπορεί να βρεθείτε να πρέπει να υπολογίσετε ένα αναμενόμενο αποτέλεσμα χρησιμοποιώντας ακριβώς τον ίδιο αλγόριθμο με τον κώδικα υπό δοκιμή, που είναι ένα κλασικό αντι-μοτίβο δοκιμών.

Οι κλήσεις στον κατασκευαστή ημερομηνίας καταγράφονται στα χαρακτηριστικά του `mock_date` (`call_count` και φίλοι), τα οποία μπορεί επίσης να είναι χρήσιμα για τις δοκιμές σας.

Ένας εναλλακτικός τρόπος αντιμετώπισης της προσομοίωσης ημερομηνιών ή άλλων ενσωματωμένων κλάσεων, συζητείται σε αυτή την καταχώρηση ιστολογίου.

### Mocking a generator method

Μια γεννήτρια Python είναι μια συνάρτηση ή μέθοδος που χρησιμοποιεί τη `yield` δήλωση για να επιστρέψει μια σειρά τιμών όταν γίνεται επανάληψη πάνω της [1].

Μια μέθοδος / συνάρτηση γεννήτριας καλείται για να επιστρέψει το αντικείμενο γεννήτριας. Είναι το αντικείμενο γεννήτριας που στη συνέχεια επαναλαμβάνεται. Η μέθοδος πρωτοκόλλου για την επανάληψη είναι η `__iter__()`, οπότε μπορούμε να την προσομοιώσουμε χρησιμοποιώντας μια `MagicMock`.

Εδώ είναι ένα παράδειγμα κλάσης με μια μέθοδο «iter» που υλοποιείται ως γεννήτρια:

    >>> class Foo:
    ...     def iter(self):
    ...         for i in [1, 2, 3]:
    ...             yield i
    ...
    >>> foo = Foo()
    >>> list(foo.iter())
    [1, 2, 3]

Πώς θα προσομοιώναμε αυτή την κλάση, και συγκεκριμένα τη μέθοδο της «iter»;

Για να διαμορφώσουμε τις τιμές που επιστρέφονται από την επανάληψη (ρητά στην κλήση στη `list`), πρέπει να διαμορφώσουμε το αντικείμενο που επιστρέφεται από την κλήση στο `foo.iter()`.

    >>> mock_foo = MagicMock()
    >>> mock_foo.iter.return_value = iter([1, 2, 3])
    >>> list(mock_foo.iter())
    [1, 2, 3]

[1]

Υπάρχουν επίσης εκφράσεις γεννητριών και πιο προηγμένες χρήσεις γεννητριών, αλλά δεν μας απασχολούν εδώ. Μια πολύ καλή εισαγωγή στις γεννήτριες και πόσο ισχυρές είναι: Generator Tricks for Systems Programmers.

### Εφαρμογή της ίδιας επιδιόρθωσης σε κάθε μέθοδο δοκιμής

Εάν θέλετε να υπάρχουν πολλές επιδιορθώσεις για πολλαπλές μεθόδους δοκιμής, ο προφανής τρόπος είναι να εφαρμόσετε τους διακοσμητές επιδιόρθωσης σε κάθε μέθοδο. Αυτό μπορεί να φαίνεται σαν περιττή επανάληψη. Αντίθετα, μπορείτε να χρησιμοποιήσετε την `patch()` (σε όλες τις διάφορες μορφές της) ως διακοσμητή κλάσης. Αυτό εφαρμόζει τις επιδιορθώσεις σε όλες τις μεθόδους δοκιμής στην κλάση. Μια μέθοδος δοκιμής αναγνωρίζεται από μεθόδους των οποίων τα ονόματα ξεκινούν με `test`:

    >>> @patch('mymodule.SomeClass')
    ... class MyTest(unittest.TestCase):
    ...
    ...     def test_one(self, MockSomeClass):
    ...         self.assertIs(mymodule.SomeClass, MockSomeClass)
    ...
    ...     def test_two(self, MockSomeClass):
    ...         self.assertIs(mymodule.SomeClass, MockSomeClass)
    ...
    ...     def not_a_test(self):
    ...         return 'something'
    ...
    >>> MyTest('test_one').test_one()
    >>> MyTest('test_two').test_two()
    >>> MyTest('test_two').not_a_test()
    'something'

Ένας εναλλακτικός τρόπος διαχείρισης των επιδιορθώσεων είναι η χρήση των patch methods: start and stop. Αυτά σας επιτρέπουν να μετακινήσετε την επιδιόρθωση στις μεθόδους σας `setUp` και `tearDown`.

    >>> class MyTest(unittest.TestCase):
    ...     def setUp(self):
    ...         self.patcher = patch('mymodule.foo')
    ...         self.mock_foo = self.patcher.start()
    ...
    ...     def test_foo(self):
    ...         self.assertIs(mymodule.foo, self.mock_foo)
    ...
    ...     def tearDown(self):
    ...         self.patcher.stop()
    ...
    >>> MyTest('test_foo').run()

Εάν χρησιμοποιήσετε αυτήν την τεχνική, πρέπει να διασφαλίσετε ότι η επιδιόρθωση αναιρείται καλώντας το `stop`. Αυτό μπορεί να είναι πιο περίπλοκο από ό,τι μπορεί να νομίζετε, επειδή εάν γίνει raise μια εξαίρεση στο setUp, τότε το tearDown δεν καλείται. Η `unittest.TestCase.addCleanup()` καθιστά αυτό πιο εύκολο:

    >>> class MyTest(unittest.TestCase):
    ...     def setUp(self):
    ...         patcher = patch('mymodule.foo')
    ...         self.addCleanup(patcher.stop)
    ...         self.mock_foo = patcher.start()
    ...
    ...     def test_foo(self):
    ...         self.assertIs(mymodule.foo, self.mock_foo)
    ...
    >>> MyTest('test_foo').run()

### Mocking unbound methods

Μερικές φορές μια δοκιμή χρειάζεται να επιδιορθώσει μια μη δεσμευμένη μέθοδο, που σημαίνει την επιδιόρθωση της μεθόδου στην κλάση αντί για το στιγμιότυπο. Για να κάνετε δηλώσεις σχετικά με το ποια αντικείμενα καλούσαν αυτή τη συγκεκριμένη μέθοδο, πρέπει να περάσετε το `self` ως πρώτο όρισμα. Το πρόβλημα είναι ότι δεν μπορείτε να επιδιορθώσετε με μια προσομοίωση για αυτό, επειδή αν αντικαταστήσετε μια μη δεσμευμένη μέθοδο με μια προσομοίωση, δεν γίνεται δεσμευμένη μέθοδος όταν ανακτάται από το στιγμιότυπο, και έτσι δεν περνάει το `self`. Η λύση είναι να επιδιορθώσετε τη μη δεσμευμένη μέθοδο με μια πραγματική συνάρτηση αντί για αυτό. Ο διακοσμητής `patch()` το καθιστά τόσο απλό να επιδιορθώνετε μεθόδους με μια προσομοίωση που η ανάγκη δημιουργίας μιας πραγματικής συνάρτησης γίνεται ενοχλητική.

Εάν περάσετε το `autospec=True` στην επιδιόρθωση, τότε γίνεται η επιδιόρθωση με ένα πραγματικό αντικείμενο συνάρτησης. Αυτό το αντικείμενο συνάρτησης έχει την ίδια υπογραφή με αυτό που αντικαθιστά, αλλά αναθέτει σε μια προσομοίωση κάτω από την κουκούλα. Ακόμα λαμβάνετε την προσομοίωσή σας που δημιουργείται αυτόματα με ακριβώς τον ίδιο τρόπο όπως πριν. Αυτό που σημαίνει, όμως, είναι ότι αν το χρησιμοποιήσετε για να επιδιορθώσετε μια μη δεσμευμένη μέθοδο σε μια κλάση, η προσομοιωμένη συνάρτηση θα μετατραπεί σε δεσμευμένη μέθοδο εάν ανακτηθεί από ένα στιγμιότυπο. Θα έχει το `self` που περνάει ως πρώτο όρισμα, που είναι ακριβώς αυτό που χρειαζόταν:

    >>> class Foo:
    ...   def foo(self):
    ...     pass
    ...
    >>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:
    ...   mock_foo.return_value = 'foo'
    ...   foo = Foo()
    ...   foo.foo()
    ...
    'foo'
    >>> mock_foo.assert_called_once_with(foo)

Εάν δεν χρησιμοποιήσουμε το `autospec=True`, τότε η μη δεσμευμένη μέθοδος επιδιορθώνεται με ένα στιγμιότυπο Mock αντίθετα, και δεν καλείται με το `self`.

### Έλεγχος πολλαπλών κλήσεων με προσομοίωση

Η προσομοίωση έχει ένα ωραίο API για τη δημιουργία δηλώσεων σχετικά με το πώς χρησιμοποιούνται τα αντικείμενα προσομοίωσής σας.

    >>> mock = Mock()
    >>> mock.foo_bar.return_value = None
    >>> mock.foo_bar('baz', spam='eggs')
    >>> mock.foo_bar.assert_called_with('baz', spam='eggs')

Εάν η προσομοίωσή σας καλείται μόνο μία φορά, μπορείτε να χρησιμοποιήσετε τη `assert_called_once_with()` μέθοδο που επίσης δηλώνει ότι το `call_count` είναι ένα.

    >>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
    >>> mock.foo_bar()
    >>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
    Traceback (most recent call last):
        ...
    AssertionError: Expected 'foo_bar' to be called once. Called 2 times.
    Calls: [call('baz', spam='eggs'), call()].

Και οι δύο `assert_called_with` και `assert_called_once_with` κάνουν δηλώσεις σχετικά με την τελευταία κλήση. Εάν η προσομοίωσή σας πρόκειται να καλείται πολλές φορές, και θέλετε να κάνετε δηλώσεις σχετικά με όλες αυτές τις κλήσεις, μπορείτε να χρησιμοποιήσετε το `call_args_list`:

    >>> mock = Mock(return_value=None)
    >>> mock(1, 2, 3)
    >>> mock(4, 5, 6)
    >>> mock()
    >>> mock.call_args_list
    [call(1, 2, 3), call(4, 5, 6), call()]

Ο βοηθός `call` καθιστά εύκολο να κάνετε δηλώσεις σχετικά με αυτές τις κλήσεις. Μπορείτε να δημιουργήσετε μια λίστα με τις αναμενόμενες κλήσεις και να τη συγκρίνετε με το `call_args_list`. Αυτό μοιάζει αξιοσημείωτα με το repr του `call_args_list`:

    >>> expected = [call(1, 2, 3), call(4, 5, 6), call()]
    >>> mock.call_args_list == expected
    True

### Αντιμετώπιση μεταβλητών ορισμάτων

Μια άλλη κατάσταση που είναι σπάνια, αλλά μπορεί να σας δαγκώσει, είναι όταν η προσομοίωσή σας καλείται με μεταβλητά ορίσματα. Το `call_args` και το `call_args_list` αποθηκεύουν αναφορές στα ορίσματα. Εάν τα ορίσματα μεταβλητοποιηθούν από τον κώδικα υπό δοκιμή, τότε δεν μπορείτε πλέον να κάνετε δηλώσεις σχετικά με το ποιες ήταν οι τιμές όταν κλήθηκε η προσομοίωση.

Εδώ είναι μερικοί παραδείγματα κώδικα που δείχνουν το πρόβλημα. Φανταστείτε τις παρακάτω συναρτήσεις ορισμένες στο “mymodule”:

    def frob(val):
        pass

    def grob(val):
        "First frob and then clear val"
        frob(val)
        val.clear()

Όταν προσπαθήσουμε να δοκιμάσουμε ότι το `grob` καλεί το `frob` με το σωστό όρισμα, δείτε τι συμβαίνει:

    >>> with patch('mymodule.frob') as mock_frob:
    ...     val = {6}
    ...     mymodule.grob(val)
    ...
    >>> val
    set()
    >>> mock_frob.assert_called_with({6})
    Traceback (most recent call last):
        ...
    AssertionError: Expected: (({6},), {})
    Called with: ((set(),), {})

Μια πιθανότητα θα ήταν για την προσομοίωση να αντιγράψει τα ορίσματα που περνάτε. Αυτό θα μπορούσε στη συνέχεια να προκαλέσει προβλήματα εάν κάνετε δηλώσεις που βασίζονται στην ταυτότητα αντικειμένου για την ισότητα.

Here’s one solution that uses the `side_effect` functionality. If you provide a `side_effect` function for a mock then `side_effect` will be called with the same args as the mock. This gives us an opportunity to copy the arguments and store them for later assertions. In this example we’re using another mock to store the arguments so that we can use the mock methods for doing the assertion. Again a helper function sets this up for us.

    >>> from copy import deepcopy
    >>> from unittest.mock import Mock, patch, DEFAULT
    >>> def copy_call_args(mock):
    ...     new_mock = Mock()
    ...     def side_effect(*args, **kwargs):
    ...         args = deepcopy(args)
    ...         kwargs = deepcopy(kwargs)
    ...         new_mock(*args, **kwargs)
    ...         return DEFAULT
    ...     mock.side_effect = side_effect
    ...     return new_mock
    ...
    >>> with patch('mymodule.frob') as mock_frob:
    ...     new_mock = copy_call_args(mock_frob)
    ...     val = {6}
    ...     mymodule.grob(val)
    ...
    >>> new_mock.assert_called_with({6})
    >>> new_mock.call_args
    call({6})

Το `copy_call_args` καλείται με την προσομοίωση που θα κληθεί. Επιστρέφει μια νέα προσομοίωση στην οποία κάνουμε τη δήλωση. Η συνάρτηση `side_effect` κάνει ένα αντίγραφο των ορισμάτων και καλεί τη `new_mock` με το αντίγραφο.

Σημείωση

Εάν η προσομοίωσή σας πρόκειται να χρησιμοποιηθεί μόνο μία φορά, υπάρχει ένας ευκολότερος τρόπος ελέγχου των ορισμάτων στο σημείο που καλούνται. Μπορείτε απλά να κάνετε τον έλεγχο μέσα σε μια `side_effect` συνάρτηση.

    >>> def side_effect(arg):
    ...     assert arg == {6}
    ...
    >>> mock = Mock(side_effect=side_effect)
    >>> mock({6})
    >>> mock(set())
    Traceback (most recent call last):
        ...
    AssertionError

Μια εναλλακτική προσέγγιση είναι να δημιουργήσετε μια υποκλάση της `Mock` ή `MagicMock` που αντιγράφει (χρησιμοποιώντας τη `copy.deepcopy()`) τα ορίσματα. Εδώ είναι ένα παράδειγμα υλοποίησης:

    >>> from copy import deepcopy
    >>> class CopyingMock(MagicMock):
    ...     def __call__(self, /, *args, **kwargs):
    ...         args = deepcopy(args)
    ...         kwargs = deepcopy(kwargs)
    ...         return super().__call__(*args, **kwargs)
    ...
    >>> c = CopyingMock(return_value=None)
    >>> arg = set()
    >>> c(arg)
    >>> arg.add(1)
    >>> c.assert_called_with(set())
    >>> c.assert_called_with(arg)
    Traceback (most recent call last):
        ...
    AssertionError: expected call not found.
    Expected: mock({1})
    Actual: mock(set())
    >>> c.foo
    <CopyingMock name='mock.foo' id='...'>

Όταν υποκλάσετε το `Mock` ή το `MagicMock`, όλα τα δυναμικά δημιουργημένα χαρακτηριστικά, και το `return_value` θα χρησιμοποιούν την υποκλάση σας αυτόματα. Αυτό σημαίνει ότι όλα τα παιδιά ενός `CopyingMock` θα έχουν επίσης τον τύπο `CopyingMock`.

### Nesting patches

Η χρήση της επιδιόρθωσης ως διαχειριστής περιβάλλοντος είναι ωραία, αλλά αν κάνετε πολλαπλές επιδιορθώσεις, μπορεί να καταλήξετε με επικαλυπτόμενες δηλώσεις with που εσοχίζουν όλο και περισσότερο προς τα δεξιά:

    >>> class MyTest(unittest.TestCase):
    ...
    ...     def test_foo(self):
    ...         with patch('mymodule.Foo') as mock_foo:
    ...             with patch('mymodule.Bar') as mock_bar:
    ...                 with patch('mymodule.Spam') as mock_spam:
    ...                     assert mymodule.Foo is mock_foo
    ...                     assert mymodule.Bar is mock_bar
    ...                     assert mymodule.Spam is mock_spam
    ...
    >>> original = mymodule.Foo
    >>> MyTest('test_foo').test_foo()
    >>> assert mymodule.Foo is original

Με τις λειτουργίες καθαρισμού του unittest και το patch methods: start and stop μπορούμε να επιτύχουμε το ίδιο αποτέλεσμα χωρίς την εμφωλευμένη εσοχή. Μια απλή βοηθητική μέθοδος, `create_patch`, τοποθετεί την επιδιόρθωση στη θέση της και επιστρέφει την δημιουργημένη προσομοίωση για εμάς:

    >>> class MyTest(unittest.TestCase):
    ...
    ...     def create_patch(self, name):
    ...         patcher = patch(name)
    ...         thing = patcher.start()
    ...         self.addCleanup(patcher.stop)
    ...         return thing
    ...
    ...     def test_foo(self):
    ...         mock_foo = self.create_patch('mymodule.Foo')
    ...         mock_bar = self.create_patch('mymodule.Bar')
    ...         mock_spam = self.create_patch('mymodule.Spam')
    ...
    ...         assert mymodule.Foo is mock_foo
    ...         assert mymodule.Bar is mock_bar
    ...         assert mymodule.Spam is mock_spam
    ...
    >>> original = mymodule.Foo
    >>> MyTest('test_foo').run()
    >>> assert mymodule.Foo is original

### Προσομοίωση ενός λεξικού με το MagicMock

Ίσως θέλετε να προσομοιώσετε ένα λεξικό ή άλλο αντικείμενο κοντέινερ, καταγράφοντας όλη την πρόσβαση σε αυτό, ενώ εξακολουθεί να συμπεριφέρεται σαν λεξικό.

Μπορούμε να το κάνουμε αυτό με την `MagicMock`, η οποία θα συμπεριφέρεται σαν λεξικό, και χρησιμοποιώντας το `side_effect` για να αναθέσουμε την πρόσβαση στο λεξικό σε ένα πραγματικό υποκείμενο λεξικό που είναι υπό τον έλεγχό μας.

Όταν οι μέθοδοι `__getitem__()` και `__setitem__()` του `MagicMock` μας καλούνται (κανονική πρόσβαση λεξικού), τότε το `side_effect` καλείται με το κλειδί (και στην περίπτωση του `__setitem__` και την τιμή επίσης). Μπορούμε επίσης να ελέγξουμε τι επιστρέφεται.

Μετά τη χρήση του `MagicMock`, μπορούμε να χρησιμοποιήσουμε χαρακτηριστικά όπως το `call_args_list` για να κάνουμε δηλώσεις σχετικά με το πώς χρησιμοποιήθηκε το λεξικό:

    >>> my_dict = {'a': 1, 'b': 2, 'c': 3}
    >>> def getitem(name):
    ...      return my_dict[name]
    ...
    >>> def setitem(name, val):
    ...     my_dict[name] = val
    ...
    >>> mock = MagicMock()
    >>> mock.__getitem__.side_effect = getitem
    >>> mock.__setitem__.side_effect = setitem

Σημείωση

Μια εναλλακτική λύση στη χρήση του `MagicMock` είναι να χρησιμοποιήσετε το `Mock` και να παρέχετε μόνο τις μαγικές μεθόδους που θέλετε συγκεκριμένα:

    >>> mock = Mock()
    >>> mock.__getitem__ = Mock(side_effect=getitem)
    >>> mock.__setitem__ = Mock(side_effect=setitem)

Μια τρίτη επιλογή είναι να χρησιμοποιήσετε το `MagicMock` αλλά περνώντας το `dict` ως όρισμα spec (ή spec_set), έτσι ώστε το δημιουργημένο `MagicMock` να έχει διαθέσιμες μόνο τις μαγικές μεθόδους λεξικού:

    >>> mock = MagicMock(spec_set=dict)
    >>> mock.__getitem__.side_effect = getitem
    >>> mock.__setitem__.side_effect = setitem

Με αυτές τις λειτουργίες side effect στη θέση τους, το `mock` θα συμπεριφέρεται σαν ένα κανονικό λεξικό αλλά καταγράφοντας την πρόσβαση. Ακόμα κάνει raise μια `KeyError` εάν προσπαθήσετε να αποκτήσετε πρόσβαση σε ένα κλειδί που δεν υπάρχει.

    >>> mock['a']
    1
    >>> mock['c']
    3
    >>> mock['d']
    Traceback (most recent call last):
        ...
    KeyError: 'd'
    >>> mock['b'] = 'fish'
    >>> mock['d'] = 'eggs'
    >>> mock['b']
    'fish'
    >>> mock['d']
    'eggs'

Μετά τη χρήση του, μπορείτε να κάνετε δηλώσεις σχετικά με την πρόσβαση χρησιμοποιώντας τις κανονικές μεθόδους και χαρακτηριστικά προσομοίωσης:

    >>> mock.__getitem__.call_args_list
    [call('a'), call('c'), call('d'), call('b'), call('d')]
    >>> mock.__setitem__.call_args_list
    [call('b', 'fish'), call('d', 'eggs')]
    >>> my_dict
    {'a': 1, 'b': 'fish', 'c': 3, 'd': 'eggs'}

### Υποκλάσεις Mock και τα χαρακτηριστικά τους

Υπάρχουν διάφοροι λόγοι για τους οποίους μπορεί να θέλετε να υποκλάσετε την `Mock`. Ένας λόγος μπορεί να είναι η προσθήκη βοηθητικών μεθόδων. Εδώ είναι ένα ανόητο παράδειγμα:

    >>> class MyMock(MagicMock):
    ...     def has_been_called(self):
    ...         return self.called
    ...
    >>> mymock = MyMock(return_value=None)
    >>> mymock
    <MyMock id='...'>
    >>> mymock.has_been_called()
    False
    >>> mymock()
    >>> mymock.has_been_called()
    True

Η τυπική συμπεριφορά για τα στιγμιότυπα `Mock` είναι ότι τα χαρακτηριστικά και οι επιστρεφόμενες τιμές προσομοιώσεων είναι του ίδιου τύπου με την προσομοίωση στην οποία έχουν πρόσβαση. Αυτό διασφαλίζει ότι τα χαρακτηριστικά `Mock` είναι `Mocks` και τα χαρακτηριστικά `MagicMock` είναι `MagicMocks` [2]. Έτσι, εάν υποκλάσετε για να προσθέσετε βοηθητικές μεθόδους, τότε θα είναι επίσης διαθέσιμες στα χαρακτηριστικά και στην επιστρεφόμενη τιμή προσομοίωσης των στιγμιότυπων της υποκλάσης σας.

    >>> mymock.foo
    <MyMock name='mock.foo' id='...'>
    >>> mymock.foo.has_been_called()
    False
    >>> mymock.foo()
    <MyMock name='mock.foo()' id='...'>
    >>> mymock.foo.has_been_called()
    True

Μερικές φορές αυτό είναι άβολο. Για παράδειγμα, ένας χρήστης υποκλάσει την προσομοίωση για να δημιουργήσει έναν προσαρμογέα Twisted. Η εφαρμογή αυτού και στα χαρακτηριστικά προκαλεί πραγματικά σφάλματα.

`Mock` (σε όλες τις παραλλαγές του) χρησιμοποιεί μια μέθοδο που ονομάζεται `_get_child_mock` για να δημιουργήσει αυτές τις «υπο-προσομοιώσεις» για χαρακτηριστικά και επιστρεφόμενες τιμές. Μπορείτε να αποτρέψετε τη χρήση της υποκλάσης σας για χαρακτηριστικά, υπερκαλύπτοντας αυτή τη μέθοδο. Η υπογραφή είναι ότι λαμβάνει αυθαίρετα ορίσματα λέξεων-κλειδιών (`**kwargs`) που στη συνέχεια περνιούνται στον κατασκευαστή προσομοιώσεων:

    >>> class Subclass(MagicMock):
    ...     def _get_child_mock(self, /, **kwargs):
    ...         return MagicMock(**kwargs)
    ...
    >>> mymock = Subclass()
    >>> mymock.foo
    <MagicMock name='mock.foo' id='...'>
    >>> assert isinstance(mymock, Subclass)
    >>> assert not isinstance(mymock.foo, Subclass)
    >>> assert not isinstance(mymock(), Subclass)

[2]

Μια εξαίρεση σε αυτόν τον κανόνα είναι οι μη κλητές προσομοιώσεις. Τα χαρακτηριστικά χρησιμοποιούν την κλητή παραλλαγή επειδή διαφορετικά οι μη κλητές προσομοιώσεις δεν θα μπορούσαν να έχουν κλητές μεθόδους.

### Προσομοίωση εισαγωγών με το patch.dict

Μια κατάσταση όπου η προσομοίωση μπορεί να είναι δύσκολη είναι όταν έχετε μια τοπική εισαγωγή μέσα σε μια συνάρτηση. Αυτές είναι πιο δύσκολες για προσομοίωση επειδή δεν χρησιμοποιούν ένα αντικείμενο από το χώρο ονομάτων του module που μπορούμε να επιδιορθώσουμε.

Γενικά, οι τοπικές εισαγωγές πρέπει να αποφεύγονται. Κάποιες φορές γίνονται για να αποτραπούν οι κυκλικές εξαρτήσεις, για τις οποίες υπάρχει συνήθως ένας πολύ καλύτερος τρόπος για να λυθεί το πρόβλημα (αναδιαμόρφωση του κώδικα) ή για να αποτραπούν τα «προκαταβολικά κόστη» καθυστερώντας την εισαγωγή. Αυτό μπορεί επίσης να λυθεί με καλύτερους τρόπους από μια άνευ όρων τοπική εισαγωγή (αποθηκεύστε το module ως χαρακτηριστικό κλάσης ή module και κάντε την εισαγωγή μόνο στην πρώτη χρήση).

Ανεξάρτητα από αυτό, υπάρχει ένας τρόπος να χρησιμοποιήσετε το `mock` για να επηρεάσετε τα αποτελέσματα μιας εισαγωγής. Η εισαγωγή ανακτά ένα αντικείμενο από το λεξικό `sys.modules`. Σημειώστε ότι ανακτά ένα αντικείμενο, το οποίο δεν χρειάζεται να είναι ένα module. Η εισαγωγή ενός module για πρώτη φορά έχει ως αποτέλεσμα τη τοποθέτηση ενός αντικειμένου module στο `sys.modules`, οπότε συνήθως όταν εισάγετε κάτι, λαμβάνετε πίσω ένα module. Ωστόσο, αυτό δεν χρειάζεται να είναι η περίπτωση.

Αυτό σημαίνει ότι μπορείτε να χρησιμοποιήσετε το `patch.dict()` για να τοποθετήσετε προσωρινά μια προσομοίωση στο `sys.modules`. Οποιεσδήποτε εισαγωγές ενώ αυτή η επιδιόρθωση είναι ενεργή θα ανακτούν την προσομοίωση. Όταν ολοκληρωθεί η επιδιόρθωση (η διακοσμημένη συνάρτηση εξέρχεται, το σώμα της δήλωσης with ολοκληρώνεται ή καλείται το `patcher.stop()`), τότε ό,τι υπήρχε προηγουμένως θα αποκατασταθεί με ασφάλεια.

Εδώ είναι ένα παράδειγμα που προσομοιώνει το module “fooble”.

    >>> import sys
    >>> mock = Mock()
    >>> with patch.dict('sys.modules', {'fooble': mock}):
    ...    import fooble
    ...    fooble.blob()
    ...
    <Mock name='mock.blob()' id='...'>
    >>> assert 'fooble' not in sys.modules
    >>> mock.blob.assert_called_once_with()

Όπως μπορείτε να δείτε, το `import fooble` πετυχαίνει, αλλά κατά την έξοδο δεν παραμένει κανένα “fooble” στο `sys.modules`.

Αυτό λειτουργεί επίσης για τη μορφή `from module import name`:

    >>> mock = Mock()
    >>> with patch.dict('sys.modules', {'fooble': mock}):
    ...    from fooble import blob
    ...    blob.blip()
    ...
    <Mock name='mock.blob.blip()' id='...'>
    >>> mock.blob.blip.assert_called_once_with()

Με λίγη περισσότερη δουλειά, μπορείτε επίσης να προσομοιώσετε εισαγωγές πακέτων:

    >>> mock = Mock()
    >>> modules = {'package': mock, 'package.module': mock.module}
    >>> with patch.dict('sys.modules', modules):
    ...    from package.module import fooble
    ...    fooble()
    ...
    <Mock name='mock.module.fooble()' id='...'>
    >>> mock.module.fooble.assert_called_once_with()

### Παρακολούθηση της σειράς κλήσεων και λιγότερο λεκτικές δηλώσεις κλήσεων

Η `Mock` σας επιτρέπει να παρακολουθείτε τη σειρά των κλήσεων μεθόδων στις προσομοιώσεις σας μέσω του χαρακτηριστικού `method_calls`. Αυτό δεν σας επιτρέπει να παρακολουθείτε τη σειρά των κλήσεων μεταξύ ξεχωριστών αντικειμένων προσομοίωσης, ωστόσο μπορούμε να χρησιμοποιήσουμε το `mock_calls` για να επιτύχουμε το ίδιο αποτέλεσμα.

Επειδή οι προσομοιώσεις παρακολουθούν τις κλήσεις σε υπο-προσομοιώσεις στο `mock_calls`, και η πρόσβαση σε ένα αυθαίρετο χαρακτηριστικό μιας προσομοίωσης δημιουργεί μια υπο-προσομοίωση, μπορούμε να δημιουργήσουμε τις ξεχωριστές προσομοιώσεις μας από μια γονική. Οι κλήσεις σε αυτές τις υπο-προσομοιώσεις θα καταγράφονται στη συνέχεια, με σειρά, στο `mock_calls` της γονικής:

    >>> manager = Mock()
    >>> mock_foo = manager.foo
    >>> mock_bar = manager.bar

    >>> mock_foo.something()
    <Mock name='mock.foo.something()' id='...'>
    >>> mock_bar.other.thing()
    <Mock name='mock.bar.other.thing()' id='...'>

    >>> manager.mock_calls
    [call.foo.something(), call.bar.other.thing()]

Μπορούμε στη συνέχεια να κάνουμε δηλώσεις σχετικά με τις κλήσεις, συμπεριλαμβανομένης της σειράς, συγκρίνοντας με το χαρακτηριστικό `mock_calls` στην προσομοίωση διαχειριστή:

    >>> expected_calls = [call.foo.something(), call.bar.other.thing()]
    >>> manager.mock_calls == expected_calls
    True

Αν το `patch` δημιουργεί και τοποθετεί τις προσομοιώσεις σας, τότε μπορείτε να τις επισυνάψετε σε μια διαχειριστική προσομοίωση χρησιμοποιώντας τη μέθοδο `attach_mock()`. Μετά την επισύναψη, οι κλήσεις θα καταγράφονται στο `mock_calls` του διαχειριστή.

    >>> manager = MagicMock()
    >>> with patch('mymodule.Class1') as MockClass1:
    ...     with patch('mymodule.Class2') as MockClass2:
    ...         manager.attach_mock(MockClass1, 'MockClass1')
    ...         manager.attach_mock(MockClass2, 'MockClass2')
    ...         MockClass1().foo()
    ...         MockClass2().bar()
    <MagicMock name='mock.MockClass1().foo()' id='...'>
    <MagicMock name='mock.MockClass2().bar()' id='...'>
    >>> manager.mock_calls
    [call.MockClass1(),
    call.MockClass1().foo(),
    call.MockClass2(),
    call.MockClass2().bar()]

Εάν έχουν γίνει πολλές κλήσεις, αλλά σας ενδιαφέρει μόνο μια συγκεκριμένη ακολουθία από αυτές, τότε μια εναλλακτική είναι να χρησιμοποιήσετε τη μέθοδο `assert_has_calls()`. Αυτή λαμβάνει μια λίστα κλήσεων (κατασκευασμένη με το `call` αντικείμενο). Εάν αυτή η ακολουθία κλήσεων βρίσκεται στο `mock_calls`, τότε η δήλωση πετυχαίνει.

    >>> m = MagicMock()
    >>> m().foo().bar().baz()
    <MagicMock name='mock().foo().bar().baz()' id='...'>
    >>> m.one().two().three()
    <MagicMock name='mock.one().two().three()' id='...'>
    >>> calls = call.one().two().three().call_list()
    >>> m.assert_has_calls(calls)

Ακόμα κι αν η αλυσιδωτή κλήση `m.one().two().three()` δεν είναι οι μόνες κλήσεις που έχουν γίνει στην προσομοίωση, η δήλωση εξακολουθεί να πετυχαίνει.

Μερικές φορές μια προσομοίωση μπορεί να έχει γίνει πολλές κλήσεις σε αυτήν, και σας ενδιαφέρει μόνο να κάνετε δηλώσεις για μερικές από αυτές τις κλήσεις. Μπορεί να μην σας ενδιαφέρει καν η σειρά. Σε αυτή την περίπτωση, μπορείτε να περάσετε το `any_order=True` στο `assert_has_calls`:

    >>> m = MagicMock()
    >>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')
    (...)
    >>> calls = [call.fifty('50'), call(1), call.seven(7)]
    >>> m.assert_has_calls(calls, any_order=True)

### Πιο σύνθετη αντιστοίχιση ορισμάτων

Χρησιμοποιώντας την ίδια βασική έννοια με το `ANY`, μπορούμε να υλοποιήσουμε αντιστοιχίσεις για να κάνουμε πιο σύνθετες δηλώσεις σχετικά με αντικείμενα που χρησιμοποιούνται ως ορίσματα σε προσομοιώσεις.

Ας υποθέσουμε ότι αναμένουμε να περαστεί κάποιο αντικείμενο σε μια προσομοίωση που κανονικά συγκρίνεται ως ίσο βάσει της ταυτότητας του αντικειμένου (που είναι το προεπιλεγμένο της Python για καθορισμένες από τον χρήστη κλάσεις). Για να χρησιμοποιήσουμε τη `assert_called_with()`, θα πρέπει να περάσουμε το ακριβώς ίδιο αντικείμενο. Εάν μας ενδιαφέρουν μόνο μερικά από τα χαρακτηριστικά αυτού του αντικειμένου, τότε μπορούμε να δημιουργήσουμε έναν αντιστοιχιστή που θα ελέγχει αυτά τα χαρακτηριστικά για εμάς.

Μπορείτε να δείτε σε αυτό το παράδειγμα πώς μια “τυπική” κλήση στο `assert_called_with` δεν είναι επαρκής:

    >>> class Foo:
    ...     def __init__(self, a, b):
    ...         self.a, self.b = a, b
    ...
    >>> mock = Mock(return_value=None)
    >>> mock(Foo(1, 2))
    >>> mock.assert_called_with(Foo(1, 2))
    Traceback (most recent call last):
        ...
    AssertionError: expected call not found.
    Expected: mock(<__main__.Foo object at 0x...>)
    Actual: mock(<__main__.Foo object at 0x...>)

Μια συνάρτηση σύγκρισης για την κλάση μας `Foo` μπορεί να μοιάζει κάπως έτσι:

    >>> def compare(self, other):
    ...     if not type(self) == type(other):
    ...         return False
    ...     if self.a != other.a:
    ...         return False
    ...     if self.b != other.b:
    ...         return False
    ...     return True
    ...

Και ένα αντικείμενο αντιστοίχισης που μπορεί να χρησιμοποιεί συναρτήσεις σύγκρισης όπως αυτή για τη λειτουργία ισότητας του θα μοιάζει κάπως έτσι:

    >>> class Matcher:
    ...     def __init__(self, compare, some_obj):
    ...         self.compare = compare
    ...         self.some_obj = some_obj
    ...     def __eq__(self, other):
    ...         return self.compare(self.some_obj, other)
    ...

Βάζοντας όλα αυτά μαζί:

    >>> match_foo = Matcher(compare, Foo(1, 2))
    >>> mock.assert_called_with(match_foo)

Το `Matcher` δημιουργείται με τη συνάρτηση σύγκρισης και το αντικείμενο που θέλουμε να συγκρίνουμε. Στο `assert_called_with`, θα κληθεί η μέθοδος ισότητας του `Matcher`, η οποία συγκρίνει το αντικείμενο με το οποίο κλήθηκε η προσομοίωση με αυτό που δημιουργήσαμε τον αντιστοιχιστή μας. Εάν ταιριάζουν, τότε το `assert_called_with` περνάει, και αν δεν ταιριάζουν, γίνεται raise μια `AssertionError`:

    >>> match_wrong = Matcher(compare, Foo(3, 4))
    >>> mock.assert_called_with(match_wrong)
    Traceback (most recent call last):
        ...
    AssertionError: Expected: ((<Matcher object at 0x...>,), {})
    Called with: ((<Foo object at 0x...>,), {})

Με λίγη τροποποίηση, θα μπορούσατε να κάνετε τη συνάρτηση σύγκρισης να κάνει raise απευθείας την `AssertionError` και να παρέχει ένα πιο χρήσιμο μήνυμα αποτυχίας.

Από την έκδοση 1.5, η βιβλιοθήκη δοκιμών Python PyHamcrest παρέχει παρόμοια λειτουργικότητα, που μπορεί να είναι χρήσιμη εδώ, με τη μορφή του αντιστοιχιστή ισότητας (hamcrest.library.integration.match_equality).