Comment ne pas modifier les tests Python

Et sortez les résultats des tests en dehors du code. Cet article concerne l'automatisation et l'augmentation de la commodité des tests en Python.


image


Introduction


J'avais un projet en développement depuis plusieurs années. Il n'y avait aucun test dans le projet. Et il avait également des dépendances actives avec d'autres équipes, ce qui a également influencé le résultat.


Les tests de régression ont été l'une des étapes d'un développement plus sûr. Son essence est de comparer les données calculées avec le dernier résultat canonisé du programme. 


python . .


:


  • . , Python xml, json . , textwrap.dedent.
  • . .
  • . , . . .


, . testoot 3.4+.


.  .


foo . :


def foo():
    return {'a': 1}

def test_simple(testoot: Testoot):
    testoot.test(foo())

pytest:


pytest -s tests

. . testoot Testoot .


:


def foo():
    return {'a': 2}

def test_simple(testoot: Testoot):
    testoot.test(foo())

AssertionError :


...
def test_simple(testoot: Testoot):
>       testoot.test(foo()))

cls = <class 'testoot.ext.pytest.PytestComparator'>, test_obj = {'a': 2}, canon_obj = {'a': 1}

    @classmethod
    def compare(cls, test_obj: any, canon_obj: any):
        """Compares objects"""
>       assert test_obj == canon_obj
E       AssertionError


. - .


--canonize.


pytest -s tests --canonize

, pytest ( --verbose ):


tests/test_console/test_console.py [tests/test_console/test_console.py::test_simple]
{'a': 2} == {'a': 1}
~Differing items:
~{'a': 2} != {'a': 1}
~Use -v to get the full diff
Canonize [yn]? y
.

. .


. : 


  • pickle. Python. VCS pickle
  • bytes. , .
  • str. utf-8 , .
  • json . json, , .

, VCS .


. .


def test_filename(testoot: Testoot):
    d = Path(testoot.storage.root_dir / 'hello.json')
    d.write_text('{}')

    testoot.test_filename(str(d))


Testoot. :


import pytest

from testoot.ext.pytest import PytestContext
from testoot.ext.simple import DefaultBaseTestoot
from testoot.pub import AskCanonizePolicy, PickleSerializer, \
    LocalDirectoryStorage, ConsoleUserInteraction, Testoot

@pytest.fixture(scope='module')
def base_testoot():
    regress = DefaultBaseTestoot(
        storage=LocalDirectoryStorage('.testoot'),
    )
    regress.storage.ensure_exists()
    yield regress

@pytest.fixture(scope='function')
def testoot(base_testoot, request):
    fixture = Testoot(base_testoot, PytestContext(request))
    yield fixture

DefaultBaseTestoot . Testoot (scope='function'). pytest request , .


DefaultBaseTestoot BaseTestoot -: .testoot pickle-, --canonize.


-:


regress = DefaultBaseTestoot(
    serializer=JsonSerializer(),
)

. pytest :


class PytestComparator(Comparator):
    @classmethod
    def compare(cls, test_obj: any, canon_obj: any):
        """Compares objects"""
        assert test_obj == canon_obj

PytestContext(request, serializer=BinarySerializer(), comparator=PytestComparator()). :


def test_str(testoot: Testoot):
    result = 'abc'
    regress.test(result, serializer=StringSerializer(), comparator=PytestComparator())

, BaseTestoot.



, . , . 


Il peut également être nécessaire de supprimer des éléments en constante évolution des données telles que la date de la dernière modification. Pour que ces changements ne pénètrent pas les données stockées.


Conclusion


Il s'agissait d'un aperçu des tests et des fonctionnalités de la bibliothèque pour Python. Je me ferai un plaisir de vous faire part de vos commentaires et suggestions!


Installation: pip3 install testoot
Documentation: https://testoot.readthedocs.io
Code source: https://github.com/aptakhin/testoot


All Articles