Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

PyTest : mes callbacks sont-elles appelées ?

+2 votes

J'ai une fonction à laquelle je spécifie plusieurs callbacks et je souhaiterais vérifier qu'elles sont bien appelées. Dans le cas d'une seule callback, il suffit de lever une exception dans la callback et d'utiliser pytest.raises. Mais avec plusieurs, la première (dans laquelle on lève une exception) empêche la seconde d'être appelée, non ? Ce test passe, mais j'ignore si ma seconde callback est appelée :

def cb1():
    raise RuntimeError("1")

def cb2():
    raise RuntimeError("2")

def f(cb1, cb2):
    cb1()
    cb2()

with pytest.raises(RuntimeError) as excinfo:
    f(cb1, cb2)

print(excinfo.value) # 1
assert False # To make PyTest print in the console
demandé 8-Fev-2015 par Vayel (1,050 points)

3 Réponses

+2 votes
 
Meilleure réponse

Sans variable globale ni log et ne prenant pas trop de place :

def generate_callback(index, states):
    """Generate a callback setting `states`[`index`] to `True`.

    :param index: The index to be toggled.
    :param states: A list of states.

    :type index: int
    :type states: list

    :return: A callback setting `states`[`index`] to `True`.
    :rtype: func
    """

    def callback(data):
        states[index] = True

    return callback

def f(cb1, cb2):
    """The function to be tested."""

    data = ...

    cb1(data)
    cb2(data)

def test_f():
    """The test."""

    states = [False, False]

    cb1 = generate_callback(0, states)
    cb2 = generate_callback(1, states)

    f(cb1, cb2)

    assert states[0]
    assert states[1]

Bien sûr, c'est valable dans le cas où vous pouvez facilement ajouter un argument states à votre callback.

Edit : j'ai créé cette classe :

class StateTesting:
    """A class to test with states."""

    def __init__(self, final_states, default=False):
        """
        :param final_states: A list of final states.
        :param default: The default state.

        :type final_states: list
        :type default: any
        """

        self.default = default
        self.len = len(final_states)

        self.final_states = final_states
        self.states = [default for i in range(self.len)]


    @staticmethod
    def test(final_states, default=False):
        """Create a decorator to test the function with states.

        :param final_states: A list of final states.
        :param default: The default state.

        :type final_states: list
        :type default: any

        :return: A decorator.
        :rtype: func
        """

        def decorator(func):
            def wrapper(*args, **kwargs):
                obj = StateTesting(final_states)

                func(obj, *args, **kwargs)

                for i in range(obj.len):
                    assert obj.states[i] == obj.final_states[i]

            return wrapper

        return decorator

    def callback(self, index):
        """Create a callback toggling `self.states`[`index`].

        :param index: The index to be toggled.
        :type index: int

        :return: A callback toggling `self.states`[`index`].
        :rtype: func
        """

        def callback(data):
            self.states[index] = self.final_states[index]

        return callback  

Elle s'utilise ainsi :

def f(cb1, cb2, cb3):
    cb1()
    cb3()


@StateTesting.test([True, False, True])
def test_f(states_obj):
    cb1 = states_obj.callback(0) # Appelée car True dans le tableau ci-dessus
    cb2 = states_obj.callback(1) # Pas appelée car False dans le tableau ci-dessus
    cb3 = states_obj.callback(2) # Appelée car True dans le tableau ci-dessus

    f(cb1, cb2, cb3)
répondu 9-Fev-2015 par Vayel (1,050 points)
edité 9-Fev-2015 par Vayel
0 votes

peut être simpliste, mais tu peux :

  • faire un fichier de log

  • faire une sortie texte (un print() )

  • utiliser une variable global que t'initialise sans rien, et que t'utilise comme tracker

simple, non exhaustif, ...

répondu 8-Fev-2015 par Lhassa (794 points)

C'est juste, mais pas très pratique. Il faut que j'enregistre les appels quelque part, puis que je consulte ce quelque part pour vérifier qu'ils sont bien là. Je conserverai ça en dernier recours.

Je ne vois pas en quoi un fichier de log ne serait pas adapté à ton problème

C'est simplement que ça revient à utiliser un truc "en plus" de PyTest.

+1 vote

Difficile de comprendre ce que tu cherches à faire concrètement avec ton vrai code. Ton exemple ne semble pas refléter les subtilités du problème que tu essayes de résoudre.

Cela dit dans ton exemple il est clair que ton second callback ne sera jamais appelé car lorsqu'une exception est levée, le code qui suit ne s’exécute pas et l'exception "remonte" jusqu'au premier endroit où elle est explicitement interceptée. Dans ce cas pytest.raises l'intercepte et l’exécution du code continue à la suite de l'endroit où elle a été interceptée. Que l'exception soit levée dans deux fonctions différentes ne change donc rien.

répondu 9-Fev-2015 par digit (152 points)

L'exception n'est qu'un moyen que j'utilise pour vérifier avec PyTest qu'une fonction est bien appelée. S'il existe d'autres moyens, ça m'intéresse.

...