Comment configurer et démonter correctement ma classe Pytest avec des tests?

101

J'utilise du sélénium pour des tests de bout en bout et je ne sais pas comment l'utiliser setup_classet les teardown_classméthodes.

Je dois configurer le navigateur dans la setup_classméthode, puis effectuer un tas de tests définis comme méthodes de classe et enfin quitter le navigateur dans la teardown_classméthode.

Mais logiquement, cela semble être une mauvaise solution, car en fait mes tests ne fonctionneront pas avec la classe, mais avec l'objet. Je passe selfparam dans chaque méthode de test, donc je peux accéder aux variables des objets:

class TestClass:
  
    def setup_class(cls):
        pass
        
    def test_buttons(self, data):
        # self.$attribute can be used, but not cls.$attribute?  
        pass
        
    def test_buttons2(self, data):
        # self.$attribute can be used, but not cls.$attribute?
        pass
        
    def teardown_class(cls):
        pass
    

Et il ne semble même pas correct de créer une instance de navigateur pour la classe. Elle devrait être créée séparément pour chaque objet, non?

Donc, j'ai besoin d'utiliser __init__et des __del__méthodes au lieu de setup_classet teardown_class?

avasin
la source

Réponses:

93

Selon la finalisation / exécution du code de démontage de Fixture , la meilleure pratique actuelle pour la configuration et le démontage consiste à utiliser yieldau lieu de return:

import pytest

@pytest.fixture()
def resource():
    print("setup")
    yield "resource"
    print("teardown")

class TestResource:
    def test_that_depends_on_resource(self, resource):
        print("testing {}".format(resource))

L'exécuter entraîne

$ py.test --capture=no pytest_yield.py
=== test session starts ===
platform darwin -- Python 2.7.10, pytest-3.0.2, py-1.4.31, pluggy-0.3.1
collected 1 items

pytest_yield.py setup
testing resource
.teardown


=== 1 passed in 0.01 seconds ===

Une autre façon d'écrire du code de démontage consiste à accepter un requestobjet -context dans votre fonction fixture et à appeler sa request.addfinalizerméthode avec une fonction qui effectue le démontage une ou plusieurs fois:

import pytest

@pytest.fixture()
def resource(request):
    print("setup")

    def teardown():
        print("teardown")
    request.addfinalizer(teardown)
    
    return "resource"

class TestResource:
    def test_that_depends_on_resource(self, resource):
        print("testing {}".format(resource))
Everett Toews
la source
Vous copiez donc ceci dans chaque fichier de test dont vous aurez besoin de la ressource?
Andy Hayden
@AndyHayden En fonction de la façon dont vous écrivez vos appareils, vous pouvez le placer dans chaque fichier de test où vous en avez besoin ou vous pouvez le placer dans un fichier conftest.py stackoverflow.com/questions/34466027/…
Everett Toews
2
Ce n'est cependant pas une configuration de classe, non? Il s'exécuterait avant chaque méthode de test de la classe.
malhar
1
Dans ce cas particulier, il n'est exécuté que lorsqu'il est utilisé comme paramètre dans une méthode de test. par exemple le resourceparam danstest_that_depends_on_resource(self, resource)
Everett Toews
64

Lorsque vous écrivez des "tests définis comme méthodes de classe" , voulez-vous vraiment dire des méthodes de classe (méthodes qui reçoivent sa classe comme premier paramètre) ou juste des méthodes régulières (méthodes qui reçoivent une instance comme premier paramètre)?

Étant donné que votre exemple utilise selfpour les méthodes de test, je suppose que c'est la dernière, il vous suffit donc d'utiliser à la setup_methodplace:

class Test:

    def setup_method(self, test_method):
        # configure self.attribute

    def teardown_method(self, test_method):
        # tear down self.attribute

    def test_buttons(self):
        # use self.attribute for test

L'instance de la méthode de test est transmise à setup_methodet teardown_method, mais peut être ignorée si votre code de configuration / suppression n'a pas besoin de connaître le contexte de test. Plus d'informations peuvent être trouvées ici .

Je vous recommande également de vous familiariser avec les appareils de py.test , car ils sont un concept plus puissant.

Bruno Oliveira
la source
1
Les fixtures sont plus faibles que les méthodes de classe: elles ne permettent pas la destruction d'objets non créés par elles (ce qui est souvent ce qui est vraiment nécessaire). A part ça, merci pour les informations.
wvxvw
Cela m'a frappé lors de la mise à niveau d'une base de code d'une version 3.0.x de pytest à une variante 4.x. Certains codes plus anciens utilisés setup_classavec des méthodes simulées et autres nécessitaient d'être modernisés. setup_class(self, foo, bar)->setup_method(self,function,foo,bar)
jxramos
28

Cela pourrait aider http://docs.pytest.org/en/latest/xunit_setup.html

Dans ma suite de tests, je regroupe mes cas de test en classes. Pour la configuration et le démontage dont j'ai besoin pour tous les cas de test de cette classe, j'utilise les méthodes de classe setup_class(cls)et teardown_class(cls).

Et pour la configuration et le démontage dont j'ai besoin pour chacun des cas de test, j'utilise le setup_method(method)etteardown_method(methods)

Exemple:

lh = <got log handler from logger module>

class TestClass:
    @classmethod
    def setup_class(cls):
        lh.info("starting class: {} execution".format(cls.__name__))

    @classmethod
    def teardown_class(cls):
        lh.info("starting class: {} execution".format(cls.__name__))

    def setup_method(self, method):
        lh.info("starting execution of tc: {}".format(method.__name__))

    def teardown_method(self, method):
        lh.info("starting execution of tc: {}".format(method.__name__))

    def test_tc1(self):
        <tc_content>
        assert 

    def test_tc2(self):
        <tc_content>
        assert

Maintenant, lorsque j'exécute mes tests, lorsque l'exécution de TestClass démarre, il enregistre les détails du moment où elle commence l'exécution, quand elle se termine l'exécution et de même pour les méthodes.

Vous pouvez ajouter d'autres étapes de configuration et de démontage que vous pourriez avoir dans les emplacements respectifs.

J'espère que ça aide!

Kiran Vemuri
la source
Salut @Kiran, quelle est la différence entre le setup_classvs setup_method?
imsrgadich
1
@imsrgadich Lorsque vous organisez vos cas de test en classes, <setup / teardown> _class est utilisé pour les étapes de configuration et de démontage de la classe et <setup / teardown> _method sont les étapes respectives pour chaque méthode de cas de test.
Kiran Vemuri
1
Merde ... maintenant je comprends! a été collé dessus pendant quelques heures. Donc, pour mettre les choses en perspective. Le <setup/teardown>_classpour toute la classe. Ici, il peut s'agir de définir le lien vers la base de données ou de charger le fichier de données. Et puis, chaque cas de test peut avoir sa propre configuration sous la forme de <setup/teardown>_method. Les choses sont bien claires maintenant. Merci beaucoup!
imsrgadich
24

Comme @Bruno l'a suggéré, l'utilisation de fixtures pytest est une autre solution accessible pour les deux classes de test ou même pour de simples fonctions de test. Voici un exemple de test des fonctions python2.7 :

import pytest

@pytest.fixture(scope='function')
def some_resource(request):
    stuff_i_setup = ["I setup"]

    def some_teardown():
        stuff_i_setup[0] += " ... but now I'm torn down..."
        print stuff_i_setup[0]
    request.addfinalizer(some_teardown)

    return stuff_i_setup[0]

def test_1_that_needs_resource(some_resource):
    print some_resource + "... and now I'm testing things..."

Ainsi, courir test_1...produit:

I setup... and now I'm testing things...
I setup ... but now I'm torn down...

Notez qu'il stuff_i_setupest référencé dans l'appareil, permettant à cet objet d'être setupet torn downpour le test avec lequel il interagit. Vous pouvez imaginer que cela pourrait être utile pour un objet persistant, comme une base de données hypothétique ou une connexion, qui doit être effacé avant chaque test pour les maintenir isolés.

ecoe
la source
13

Votre code doit fonctionner exactement comme vous vous y attendez si vous ajoutez des @classmethoddécorateurs.

@classmethod 
def setup_class(cls):
    "Runs once per class"

@classmethod 
def teardown_class(cls):
    "Runs at end of class"

Voir http://pythontesting.net/framework/pytest/pytest-xunit-style-fixtures/

Okken
la source
C'est à peu près exactement ce qui apparaît dans la documentation. Le problème que j'ai eu avec le doc était que j'avais du mal à comprendre le contexte: le soi est traditionnellement appelé soi, pas cls, donc cela me semblait bizarre, hors du contexte de la classe elle-même. Kiran (ci-dessus) fournit ce contexte.
Cognitiaclaeves
1
@Cognitiaclaeves "soi est traditionnellement appelé soi, pas cls" Oui,self est utilisé pour les méthodes d'instance, où le premier argument est l'instance d'objet spécifique sur laquelle l'opération de méthode a lieu, tandis que clsest utilisé pour @classmethods, qui sont liés à la classe et non une instance de la classe (c'est-à-dire un objet).
code_dredd