Структура тестового фреймворка на Python (nose, py.test)


(Alexey) #1

Доброго времени суток!

С недавнего времени осваиваю Python, до этого активно истользовал в тестировании связку Java+TestNG
Просмотрел несколько тествых фрейморков (nose, py.test), и так и не понял, как оргазавать тестовый сьют.
Привычная для меня структура была приблизительно такой (исходя из TestNg):
- Тестовый сьют - XML, в котором собраны тестовый классы. 1 Класс = 1 Тест.
- Тестовый класс состоит из тестовых медодов, которые впринципе соответствуют тест степам.

Не могу понять как мне сделать подобное в питоне.. что будет эквивалентом тестового класса со степами, и как собрать их в сьют....

P.S. мне необходимо будет написать свой плагин, который будет реагировать не события типа:
onTestSuiteStart
onTestCaseStart
onTestStepStart
onTestSuiteFinished и так далее..... поэтому тесты надо как-то группировать и нельзя скидывать в общий котел...

буду очень признателен за примеры)


(Mykhailo Poliarush) #2

Просто интересно, почему решили осваивать python после уже активного использования Java?

Ну тут есть как общие моменты так и отличия. В общем если говорить за unittest, это самая приближенная нативная реализация в Python. Я подскажу быстро и по беглому. Вы глядит она где-то так:

import unittest

class TestClass(unittest.TestCase):
	def test_something(self):
		pass
		
	def test_something2(self):
		raise Error("123")		
		

Для того, чтобы сделать свит нужно писать код

from tests import TestClass
import unittest

def suite():
	suite = unittest.TestSuite()
	#suite.addTest(TestClass())
	#suite.addTest(TestClass("test_something"))
	suite.addTest(unittest.makeSuite(TestClass))
	return suite
	
if __name__ == '__main__':
	unittest.main()

А если мы говорим о nose или py.test, то там нужно просто писать функции в модуле или методы в классе, которые соответствуют конвенции и они без проблем будут запускаться.

Пример:

nose

def setup_func():
    "set up test fixtures"

def teardown_func():
    "tear down test fixtures"

@with_setup(setup_func, teardown_func)
def test():
    "test ..."

nosetests test_this.py

А тест свиты обычно не описывают программно, хотя это можно сделать. Для запуска пачками используют или аттрибуты http://nose.readthedocs.org/en/latest/plugins/attrib.html или test discovery http://nose.readthedocs.org/en/latest/finding_tests.html

nosetests /path/to/file.py
nosetests /path/to/directory
nosetests -w /path/to/directory

https://nose.readthedocs.org/en/latest/usage.html#options
И вот еще пару ссылок для прочтения http://ivory.idyll.org/articles/nose-intro.html и http://pythontesting.net/framework/nose/nose-introduction/

py.test

def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

class TestClass:
    def test_one(self):
        x = "this"
        assert 'h' in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, 'check')

py.test test_sample.py

Почитайте ссылку http://pythontesting.net/framework/pytest-introduction/

proboscis

А если вы с Java перешли, то вам будет близок вот этот проект proboscis http://pythonhosted.org/proboscis/. Практически TestNG только на python. Пример кода:

import unittest
from proboscis.asserts import assert_equal
from proboscis import test

import utils

@test(groups=["unit", "numbers"])
class TestIsNegative(unittest.TestCase):
    """Confirm that utils.is_negative works correctly."""

    def test_should_return_true_for_negative_numbers(self):
        self.assertTrue(utils.is_negative(-47))

    def test_should_return_false_for_positive_numbers(self):
        self.assertFalse(utils.is_negative(56))

    def test_should_return_false_for_zero(self):
        self.assertFalse(utils.is_negative(0))

@test(groups=["unit", "strings"])
def test_reverse():
    """Make sure our complex string reversal logic works."""
    original = "hello"
    expected = "olleh"
    actual = utils.reverse(original)
    assert_equal(expected, actual)

def run_tests():
    from proboscis import TestProgram
    from tests import unit

    # Run Proboscis and exit.
    TestProgram().run_and_exit()

if __name__ == '__main__':
    run_tests()
$ python run_tests.py

test_should_return_false_for_positive_numbers (examples.unit.tests.unit.TestIsNegative) ... ok
test_should_return_false_for_zero (examples.unit.tests.unit.TestIsNegative) ... ok
test_should_return_true_for_negative_numbers (examples.unit.tests.unit.TestIsNegative) ... ok
Make sure our complex string reversal logic works. ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

(Alexey) #3

Спасибо за оперативность...

Осваивать решил для раскачки собственных скилов и нынешней популярности питона... По поводу сбора вроде как понятно - спасибо...

За пробоскис спасибо, я его уже поковывырял, но опять же я не нашел того что мне надо...

Дело в том, что пробоскискис сделан на основе nose... Для того чтобы написать слушатель тестов для nose, я наследуюсь от класса Plugin, переопределяю нужные методы и вроде как все хорошо... Но в интерфейсе nose плагина, я совсем не нахожу методов, которые срабатывают при старте тестового сьюта/класса.... Реакция есть только на "тест"... (Passed, Failture, Error).. Т.е. мне бы собрать отчет, в духе...

TestSuite1  - startTime
    test_1 - startTime - endTime - testResultStatus
    test_2 - startTime - endTime - testResultStatus
TestSute1 - endTime

TestSuite2 -  //// аналогично....

Именно чтобы в нем отражалась информация о стуктуре тестов, и можно было бы понять Когда начался сьют, когда он закончился, когда начался тест, и когда он закончился.. И собрать эту штуку нужно руками, потому как он собирается на удаленном сервере, при помощи отправки соответствующих http запросов...


(Mykhailo Poliarush) #4

Для того чтобы разобраться, что можно использовать nose смотреть в базоый класс интерфейс для плагинов, может быть найдете, то что вам нужно. Находится он тут c:\Program Files (x86)\Python27\Lib\site-packages\nose\plugins\base.py

addOptions
addDeprecated
addError
addFailure
addSkip
addSuccess
afterContext
afterDirectory
afterImport
afterTest
beforeContext
beforeDirectory
beforeImport
beforeTest
begin
configure
finalize
describeTest
formatError
formatFailure
handleError
handleFailure
loadTestsFromDir
loadTestsFromModule
loadTestsFromName
loadTestsFromNames
loadTestsFromFile
loadTestsFromPath
loadTestsFromTestCase
loadTestsFromTestClass
makeTest
options
prepareTest
prepareTestCase
prepareTestLoader
prepareTestResult
prepareTestRunner
report
setOutputStream
startContext
startTest
stopContext
stopTest
testName
wantClass
wantDirectory
wantFile
wantFunction
wantMethod
wantModule
wantModuleTests

Еще я бы посмотрел в сторону py.test. Там есть hooks http://pytest.org/latest/plugins.html#generic-runtest-hooks

pytest_runtest_setup(item)[source]
pytest_runtest_call(item)[source]
pytest_runtest_teardown(item, nextitem)[source]
pytest_runtest_makereport(item, call)[source]

А для того, чтобы посмотреть как это все можно использовать, просто берите один из плагинов и смотри исходный код. Самый подходящий наверное будет junitxml.py c:\Program Files (x86)\Python27\Lib\site-packages\_pytest\junitxml.py


(Alexey) #5

Спс.. Nose уже похоронил в этом плане... Пока еще питаю надежды на py.test


(Mykhailo Poliarush) #6

Ну ок, если написание этого кастомного листнера такая уж критичная задача для фреймворка.

Хотя, имхо, по своему опыту скажу, мне больше нравиться работать с py.test нежели с nose.


(Alexey) #7

а аргументы ??...

вообще как огранизовывают высокоуровневые (селениум) тесты на питоне ? смысл же том, что даже при условиии запуска через grid, UI - тесты порой очень долгие... и еще бОльшоая проблема - зависимости очень важны... если срывается какой-то степ, нет смысла мучать UI следующими двумя.. это конечно решаемо, как я понял, с помощью пробоскиса, а что насчет py.test -а ?


(Mykhailo Poliarush) #8

Он более гибкий в настройке чем nose. Хотя если в общем посмотреть, то фреймворки плюс минус одинаковы.

Кстати также еще забыл упомянуть об nose2. Это просто сторонняя разработка а не следующая версия nose. Там тоже есть хуки https://nose2.readthedocs.org/en/latest/plugins/printhooks.html, выбор ивентов для прослушки не думаю что подойдут под ваши нужды https://nose2.readthedocs.org/en/latest/plugins/printhooks.html, ну и там конечно есть некоторые отличительные черты в виде dsl https://nose2.readthedocs.org/en/latest/such_dsl.html и организации тестов https://nose2.readthedocs.org/en/latest/plugins/layers.html чтобы сделать запуск вот таким

Layer.setUp ->
  SubLayer.setUp ->
    Layer.testSetUp ->
      SubLayer.testSetUp ->
        TestCase.setUp
          TestCase.run
        TestCase.tearDown
      SubLayer.testTearDown <-
    Layer.testTearDown <-
  SubLayer.tearDown <-
Layer.tearDown <-

В python тесты организовывают по другому. Через пакеты, директории, названия файлов и теги в тестах, ну и в зависимости от фреймворка. Но это отдельная история.

Встроенных механизмов зависимостей нет. Тут придется или другие фреймворки искать, или самому доделывать.