Python for testers

Posts Tagged ‘техника’

Разделение stdout и stderr в python nosetest

Written by Михаил Поляруш on . Posted in Автоматизация, Работаю, Разработка

Проблема: в python есть популярный xUnit фреймворк nose, которым пользуются довольно много людей.  Когда подключаешь nose к continuous integration, то хочешь чтобы результаты были максимально видимыми и репрезентативными.  Но вот есть одна проблема, всю выходную информацию nose записывает в один поток, либо stdout либо stderr.  А как же быть, если нам нужно показать часть в stderr, а часть в stdout?

Например 

Результат прогона полностью выводиться в stderr:

#1 Test 1 … ok
#2 Test 2 … FAIL
#3 Test 3 … ok
#4 Test 4 … ok
#5 Test 5 … FAIL

======================================================================
FAIL: Test 2
———————————————————————-
Traceback (most recent call last):
  File “/home/katerina/Desktop/my_tests/color.py”, line 11, in test_2
    self.assertEquals(1, 2)
AssertionError: 1 != 2

======================================================================
FAIL: Test 5
———————————————————————-
Traceback (most recent call last):
  File “/home/katerina/Desktop/my_tests/color.py”, line 23, in test_5
    self.assertEquals(1, 7)
AssertionError: 1 != 7

———————————————————————-
XML: nosetests.xml
———————————————————————-
Ran 5 tests in 0.004s

FAILED (failures=2)


А надо чтоб результат выводился в 2 потока:

#1 Test 1 … ok
#2 Test 2 … FAIL
#3 Test 3 … ok
#4 Test 4 … ok
#5 Test 5 … FAIL

======================================================================
FAIL: Test 2
———————————————————————-
Traceback (most recent call last):
  File “/home/katerina/Desktop/my_tests/color.py”, line 11, in test_2
    self.assertEquals(1, 2)
AssertionError: 1 != 2

======================================================================
FAIL: Test 5
———————————————————————-
Traceback (most recent call last):
  File “/home/katerina/Desktop/my_tests/color.py”, line 23, in test_5
    self.assertEquals(1, 7)
AssertionError: 1 != 7

———————————————————————-
XML: nosetests.xml
———————————————————————-
Ran 5 tests in 0.004s

FAILED (failures=2)

Вся соль в том, что nose основывает весь свой запуск на unittest. А в unittest используется механизм буферизации stdout и stderr, т.е. unittest все ловит, но в последствии все уходит в один поток. Этот поток по умолчанию stderr. Его конечно можно поменять, но все равно это запись содержимого прогона в один поток или stderr или stdout.

Решение: простое решение – это переопределить TextTestResult и TextTestRunner при запуске тестов. Смотрим пример:

import unittest
import sys
import nose
from nose.core import TextTestRunner
from nose.result import TextTestResult
from cStringIO import StringIO

class Tests(unittest.TestCase):

    def test_1(self):
        """Test 1"""
        print 'something'
        self.assertEquals(1, 1)

    def test_2(self):
        """Test 2"""
        self.assertEquals(1, 1)

    def test_3(self):
        """Test 3"""
        self.assertEquals(1, 1)

    def test_4(self):
        """Test 4"""
        self.assertEquals(1, 1)

    def test_5(self):
        """Test 5"""
        print 'something'
        raise AssertionError("error")
        self.assertEquals(1, 1)

if __name__ == '__main__':
    class MyTextTestResult(TextTestResult):

        def addError(self, test, err):
            print >> sys.stderr, "%r ... error\n\t%r" % (test, err)

        def addFailure(self, test, err):
            print >> sys.stderr, "%r ... failure\n\t%r" % (test, err)

        def addSuccess(self, test):
            print >> sys.stdout, "%r ... ok" % test

    class MyTextTestRunner(TextTestRunner):

        def __init__(self):
            TextTestRunner.__init__(self, stream=StringIO())

        def _makeResult(self):
            return MyTextTestResult(self.stream,
                                    self.descriptions,
                                    self.verbosity,
                                    self.config)

    nose.main(testRunner=MyTextTestRunner())

Пример можно скачать здесь https://github.com/polusok/nose-split-stderr-stdout. Это далеко не идеальное решение, но позволит Вам быстро решить вашу задачу.

Успехов и хорошего питонирования!

З.Ы. С 05.08 стартует следующая группа по обучению программированию на python для начинающих. Еще можно записаться!

Логирование и декораторы в python

Written by Михаил Поляруш on . Posted in Автоматизация, Разработка

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

Как правильно выполнить логирование для Вашего проекта? Не знаю, каждый проект это отдельная история, которая должна рассматриваться в отдельности. (Обращайтесь, будем разбираться!) Но я хочу внести некоторые свои комментарии в данный процесс.

Меня попросили посмотреть на код и показать, как можно выводить документацию для каждого метода в лог. В python это лучше всего сделать с помощью декораторов. Декораторы – это мощная штука! В общем, меньше слов и больше кода.

import logging

def method_decorator(func):
    def wrapper(self, *argv, **kwargv):
        logging.basicConfig(filename='myapp.log', level=logging.INFO)
        logging.info(func.__doc__)
        return func(self, *argv, **kwargv)
    return wrapper

class Something1(object):

    @method_decorator
    def method1(self):
        """documentation thru method decorator for method 1"""
        pass

    def method2(self):
        """documentation thru method decorator for method 2"""
        pass

    @method_decorator
    def method3(self):
        """documentation thru method decorator for method 3"""
        pass

s1 = Something1()
s1.method1()
s1.method2()
s1.method3()

# or thru class decorator

def class_decorator(cls):
    for name, method in cls.__dict__.iteritems():
        if not name.startswith('_'):
            setattr(cls, name, method_decorator(method))
    return cls

@class_decorator
class Something2(object):

    def method1(self):
        """documentation thru class decorator for method 1"""
        pass

    def method2(self):
        """documentation thru class decorator for method 2"""
        pass

    def method3(self):
        """documentation thru class decorator for method 3"""
        pass

s2 = Something2()
s2.method1()
s2.method2()
s2.method3()

#check mixed execution
s1.method1()
s2.method1()

Т.е. мы описываем необходимый читабельный комментарий как docstring для методов, и когда эти методы вызываются, в лог записывается читабельная информация. В первом случае, можно использовать декоратор для методов, который можно использовать по требованию. А второй класс показывает, как можно включить данное логирование для всех методов класса. Или же Вы можете задать любую необходимую вам логику в class_decorator().

Вот, что мы получаем в логе:

INFO:root:documentation thru method decorator for method 1
INFO:root:documentation thru method decorator for method 3
INFO:root:documentation thru class decorator for method 1
INFO:root:documentation thru class decorator for method 2
INFO:root:documentation thru class decorator for method 3
INFO:root:documentation thru method decorator for method 1
INFO:root:documentation thru class decorator for method 1

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

import logging
import unittest
import random
from functools import wraps

def method_decorator(func):
    @wraps(func)
    def wrapper(self, *argv, **kwargv):
        logging.basicConfig(filename='myapp.log', 
            level=logging.INFO, format='%(message)s')
        logging.info("\t- %s" % func.__doc__)
        return func(self, *argv, **kwargv)
    return wrapper

def class_decorator(cls):
    for name, method in cls.__dict__.iteritems():
        if not name.startswith('_'):
            setattr(cls, name, method_decorator(method))
    return cls

class MyTestingException(Exception):

    def __init__(self, value):
        self.msg = value

    def __str__(self):
        return "%s\n%s" % (self.msg, open("myapp.log").read())

@class_decorator
class Something(object):

    def _generate_number(self):
        if random.randint(0, 10) == 5:
            raise MyTestingException("Please have a look to details:")
        return random.randint(0, 2)

    def method1(self):
        """log to system to make some actions"""
        return self._generate_number()

    def method2(self):
        """registered account with additional credits"""
        return self._generate_number()

    def method3(self):
        """buy subscription for defined account"""
        return self._generate_number()

class TestSomething(unittest.TestCase):

    def setUp(self):
        with open("myapp.log", "w") as f:
            f.truncate()
        self.s = Something()
        self.data = {'some data': [1, 2, 3]}

    def test_method1(self):
        self.s.method1()
        self.s.method2()
        self.s.method3()
        self.assertEquals(self.s.method1(), 0)

    def test_method2(self):
        self.s.method3()
        self.s.method2()
        self.s.method1()
        self.assertEquals(self.s.method2(), 1)

    def test_method3(self):
        self.s.method1()
        self.s.method3()
        self.s.method2()
        self.assertEquals(self.s.method3(), 2)

if __name__ == '__main__':
    unittest.main()

Что дает следующие результаты:

EFE

======================================================================

ERROR: test_method1 (__main__.TestSomething)

----------------------------------------------------------------------

Traceback (most recent call last):

  File "demo.py", line 64, in test_method1

    self.s.method3()

  File "demo.py", line 12, in wrapper

    return func(self, *argv, **kwargv)

  File "demo.py", line 50, in method3

    return self._generate_number()

  File "demo.py", line 37, in _generate_number

    raise MyTestingException("Please have a look to details:")

MyTestingException: Please have a look to details:

	- log to system to make some actions

	- registered account with additional credits

	- buy subscription for defined account

======================================================================

ERROR: test_method3 (__main__.TestSomething)

----------------------------------------------------------------------

Traceback (most recent call last):

  File "demo.py", line 77, in test_method3

    self.assertEquals(self.s.method3(), 2)

  File "demo.py", line 12, in wrapper

    return func(self, *argv, **kwargv)

  File "demo.py", line 50, in method3

    return self._generate_number()

  File "demo.py", line 37, in _generate_number

    raise MyTestingException("Please have a look to details:")

MyTestingException: Please have a look to details:

	- log to system to make some actions

	- buy subscription for defined account

	- registered account with additional credits

	- buy subscription for defined account

======================================================================

FAIL: test_method2 (__main__.TestSomething)

----------------------------------------------------------------------

Traceback (most recent call last):

  File "demo.py", line 71, in test_method2

    self.assertEquals(self.s.method2(), 1)

AssertionError: 2 != 1

----------------------------------------------------------------------

Ran 3 tests in 0.003s

FAILED (failures=1, errors=2)

Как всегда, пишите, если у Вас есть вопросы! Удачи и хорошего Вам питонирования! :)

Как управлять временем с помощью GMail?

Written by Михаил Поляруш on . Posted in Развитие, Размышляю

Как бы ни развивались IT технологии, но обычный старый email, которому уже больше 50 лет, до сих в обиходе у обычных пользователей. Ведь email был, есть и остается эффективный средством обмена информацией. Каждый день сотни миллионов email-ов курсируют по интернету в поисках своих респондентов. При построении своей тайм-менеджмент системы необходимо учитывать этот поток информации, тем более что через email проходит не только спам, но и задачи, обсуждения и важная информация, на основании которой необходимо выполнить какие-то действия. Gmail, на данный момент, является одним из самых известных почтовых сервисов. В своем докладе я расскажу, как эффективно использовать GMail и даже более того, расскажу, как из Gmail сделать тайм-менеджмент систему, которой вы сможете доверять и который вы сможете пользоваться везде, где у вас есть доступ в интернет.

Ранним утром 04.02.2012 itbrunch.com.ua собрал всех кому интересна тематика управлением временем (~220 чел.). Мне удалось поучаствовать в этой конференции в роли докладчика с темой “Time management + GMail = GTime“. Cистемы GTime в реальности не существует, но в своем докладе за 20 минут я рассказал, как такую систему можно построить.

Самовнушение великая сила

Written by Михаил Поляруш on . Posted in Обыденное

Самовнушение

Самовну-шение

Давно уж у меня так не болела голова. Наверное, смена погоды или может просто какие-то изменения. Настроение было паршивое. Решил воспользоваться самовнушением.

Как я это делаю:

  • Закрыть глаза
  • Представить улыбку, которая распространяется по всему телу
  • И дальше программировать себя:
    • Я здоровый
    • Я отлично себя чувствую
    • Я самый счастливый в мире человек
  • После этого можно ложиться спать или же сделать прогулку, что бы подышать свежим воздухом.

Agile манифест

Written by Михаил Поляруш on . Posted in Разработка

Консультировал одну команду по поводу тестирования в agile. Возникли мальникие дебаты по поводу сути agile.  Вот решил себе (и вам) напомнить agile манифест:

  • Люди и взаимодействие превыше процессов и инструментов
  • Работающее программное обеспечение превыше обширной документации
  • Взаимодействие с заказчиками превыше переговоров по контракту
  • Реакция на изменения превыше следованию по плану

Twitter лента

autotestinfo

Как продолжить тест при случайном появлении попапа? http://t.co/tGqX8PjPzD

mpoliarush

http://t.co/9879JVgl21 automates tests written with QUnit, Jasmine, Mocha with Expect.js assertions, Dojo Objective Harness, or YUI Test.