2
votes

Attribut moqueur à changer à chaque accès

Voici la fonction que je veux tester:

@mock.patch.object(demo_sequence.lib, 'conn')
def test_send_something(mock_conn):
    mock_conn.SSH1.recbuf = 'home'
    assert demo_sequence.lib.PASS == demo_sequence.send_something()
    mock_conn.SSH1.send.assert_any_call('ls\n')
    mock_conn.SSH1.send.assert_any_call('whoami\n')

Chaque fois que j'appelle conn.send (), la sortie de l'appel est stockée dans conn.recbuf. Pour tester cela, j'ai besoin que conn.recbuf soit différent à chaque fois qu'il est appelé. (Je sais que je pourrais simplement changer conn.recbuf pour être une chaîne contenant à la fois home et USER mais cela ne fonctionnera pas pour des fonctions plus compliquées)

Voici ce que j'ai trouvé jusqu'ici dans mon test:

def send_something():
    conn = lib.conn.SSH1
    conn.open()
    conn.send('ls\n')
    if 'home' not in conn.recbuf:
        return lib.FAIL

    conn.send('whoami\n')
    if USER not in conn.recbuf:
        return lib.FAIL
    return lib.PASS

Ceci échoue évidemment car conn.recbuf est uniquement «home» et ne contient pas USER.

J'ai besoin de quelque chose comme conn.recbuf.side_effect = ['home', USER] mais cela fonctionnera en faisant simplement référence à recbuf sans l'appeler.


4 commentaires

Ne voudriez-vous pas simplement vous moquer de envoyer pour qu'il ne fasse rien d'autre que de mettre à jour recbuff ? Il est difficile de fournir une bonne solution sans savoir quel est réellement le type de `conn`.


La bibliothèque conn est bizarre (mauvaise). SSH1 doit également être ridiculisé car il n'existe pas toujours. C'est pourquoi j'ai choisi de se moquer du niveau lib.conn.


Je pourrais aussi recommander qu'au lieu de coder en dur la valeur de conn dans send_something , vous en fassiez un argument de fonction. Ensuite, vous pouvez passer n'importe quel objet de votre choix comme argument au lieu d'avoir à utiliser mock.patch du tout.


Si vous testez unitairement send_something, il est probablement préférable de se moquer entièrement de conn. Il est également possible de faire un autre type de test spécial pour la bibliothèque lib.conn si vous voulez voir qu'elle remplit le contrat. En outre, il est possible de créer un shim / adaptateur autour de la "mauvaise" bibliothèque - si votre code utilise la bibliothèque à plusieurs endroits.


3 Réponses :


0
votes

Ce que vous recherchez est side_effect : https://docs.python.org/3/library/unittest.mock.html

Disons que mock est votre objet fictif. Ensuite, vous pouvez faire ceci:

mock() # -> a
mock() # -> b
mock() # -> c

puis chaque fois que vous appelez mock , la valeur suivante de la liste est renvoyée.

mock.side_effect = [a,b,c]


4 commentaires

Downvoté parce que c'est exactement ce qui est encore en apprentissage mentionné dans sa dernière phrase et a également expliqué pourquoi cela ne fonctionne pas: il a besoin exactement de ce comportement pour les attributs et non les callables.


Je l'ai. Vous pouvez réaliser la même chose pour les attributs en vous moquant de la méthode __getattr__ mais cela peut être un peu compliqué.


Pouvez-vous expliquer comment vous moquer de getattr pour trouver une solution à mon problème?


Lorsque vous faites a.x, il est traduit en .__ getattr __ ('x'). Donc, vous pouvez essayer de vous moquer de cela.



1
votes

Si vous pouvez changer conn.recbuf en propriété , vous pouvez utiliser un PropertyMock pour obtenir l'effet souhaité.

from unittest import mock

class Dummy:

    def __init__(self, myattribute):
        self._myattribute = myattribute

    @property
    def myattribute(self):
        return self._myattribute

def test():
    with mock.patch('__main__.Dummy.myattribute', new_callable=mock.PropertyMock) as mocked_attribute:
        mocked_attribute.side_effect = [4,5,6]
        d = Dummy("foo")
        print(d.myattribute)
        print(d.myattribute)
        print(d.myattribute)

Cependant, pour votre problème réel, je pense que les commentaires à votre question semblent inclure des approches raisonnables.


1 commentaires

Cette approche répond à mon besoin. J'ai vu quelques commentaires et réponses précédentes qui disaient d'utiliser PropertyMock mais je n'ai jamais vérifié si recbuf était une propriété immobilière. (a le décorateur de la propriété). Merci pour l'explication supplémentaire.



0
votes

J'étais aux prises avec un problème similaire plus tôt et j'ai trouvé une solution en utilisant le PropertyMock . Dans mon cas, je voulais stocker une valeur ailleurs dans le cas de test pour m'affirmer en appelant une méthode via un side_effect lorsqu'une propriété était définie sur la classe dont je me moquais.

Je pense que cela devrait fonctionne également pour votre cas (si j'ai compris ce que vous essayez de faire).

La solution consiste à utiliser un PropertyMock pour votre propriété recbuf et mettez le side_effect dedans:

from unittest.mock import MagicMock, PropertyMock

conn = MagicMock()
type(conn).recbuf = PropertMock(side_effect=['home', USER])

L'effet secondaire sera appelé chaque fois que la propriété recbuf est accédé, dans ce cas itération via ['home', USER] à chaque fois. Je suppose que vous devrez également corriger cette valeur USER pour ne pas avoir d'erreur de nom.

J'espère que cela fonctionne / aide!


0 commentaires