Python for testers

Posts Tagged ‘python’

[Рецепт] Создание вложенных словарей для тестовых нужд без особых проблем, пример на Python

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

В поддержку новой инициативы Code Recipes. Искренне надеюсь на вашу помощь и всяческую поддержку в виде новых code recipes!

Если Вы программируете на Python, то знаете что словарь является очень важной структурой. И не только является важным для самого языка программирования, а и для автоматизации тестирования на этом языке. Ведь очень часто приходиться описывать данные в формате ключ: значение. Такие данные могут быть большие по объему описываемых данных, а также не существовать в момент обращения к словарю . И если использовать стандартные механизмы словаря, то описать сложенные подсловари неудобно, а тем более если вы хотите создавать структуру данных программно в режиме runtime.

Пример, нам надо создать структуру:

{
    "server": {
        "host": "127.0.0.1",
        "port": "22"
    },
    "configuration": {
        "ssh": {
            "access": "true",
            "login": "some",
            "password": "some"
        }
    }
}

Стандартный способ решения этой задачи

data = {}
# some logic here
print data["server"]  # will raise exception due to 'KeyError: 'server''
# some logic here
data["server"] = {
    "host": "127.0.0.1",
    "port": "22"
}
# some logic here
if data["configuration"]["ssh"]["login"]: # will raise exception here
    pass

# some logic here
data["configuration"] = {
    "ssh": {
    "access": "true",
    "login": "some",
    "password": "some"
    }
}

Немного улучшенный вариант

У словаря можно использовать get метод, чтобы получить значение и не выдавать исключение и возвращать, дефолтное значение. Но если в случае с data["server"] это сработает, то сdata["configuration"]["ssh"]["login"] не сработает, так как возвращаемый объект будет типа None

data = {}
# some logic here
print data.get("server")
# some logic here
data["server"] = {
    "host": "127.0.0.1",
    "port": "22"
}
# some logic here
if data.get("configuration").get("ssh").get("login"):
    # will raise exception
    # AttributeError: 'NoneType' object has no attribute 'get'
    pass

# some logic here
data["configuration"] = {
    "ssh": {
    "access": "true",
    "login": "some",
    "password": "some"
    }
}

Но если задать дефолтные значения дляdata.get("configuration").get("ssh").get("login") чтобы не получать ошибкуAttributeError: 'NoneType' object has no attribute 'get', то можно получить нормальный результат

data = {}
# some logic here
print data.get("server")
# some logic here
data["server"] = {
    "host": "127.0.0.1",
    "port": "22"
}
# some logic here
if data.get("configuration", {}).get("ssh", {}).get("login", {}):
    # will raise exception
    # AttributeError: 'NoneType' object has no attribute 'get'
    pass

# some logic here
data["configuration"] = {
    "ssh": {
    "access": "true",
    "login": "some",
    "password": "some"
    }
}

Готовый рецепт, усовершенствованный способ

А можно использовать возможности defaultdict

from collections import defaultdict
_default_data = lambda: defaultdict(_default_data)
data = _default_data()
# some logic here
print data["server"]
# some logic here
data["server"]["host"] = "127.0.0.1"
data["server"]["port"] = "22"
# some logic here
if data["configuration"]["ssh"]["login"]:
    print ("some logic") 
# some logic here
data["configuration"]["ssh"]["access"] = "true"
data["configuration"]["ssh"]["login"] = "some"
data["configuration"]["ssh"]["password"] = "some"

import json
print json.dumps(data, indent=2)
defaultdict(<function <lambda> at 0x02565930>, {})
{
  "configuration": {
    "ssh": {
      "access": "true", 
      "login": "some", 
      "password": "some"
    }
  }, 
  "server": {
    "host": "127.0.0.1", 
    "port": "22"
  }
}

Результат получается без всяких исключений и сложностей, и мы можем задавать любую структуру из подвложенных словарей без фактического определения самих объектов, а также получаем возможность проверки словаря без исключений и удобную стандартную форму записи словаря.

Все варианты кода можно посмотреть на нашей общем репозитории примеровhttps://github.com/atinfo/at.info-knowledge-base/tree/master/programming/python/code%20recipes/generate%20nested%20dicts

Ну и кто хочет учиться python и автоматизации, милости прошу на http://lessons2.ru

Разделение 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)

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

Выступил на конференции atdays 2013

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

На этих выходных (09.02.2013) я проводил и выступал на конференции Test Automation Days. За конференцию еще буду писать отдельно, а вот просто хочу поделиться слайдами моего доклада, как быстро расширять robot framework под свои потребности на python.

ChromeDriver + Python + Browsermob proxy

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

Для бывалых автоматизаторов веб-приложений известно, как важно иметь под руками прокси сервер, с помощью которого можно решить целый ряд вопросов и проблем с веб-автоматизации. NTLM авторизация, блокирование сторонних сайтов не относящихся к тестированию вашего веб-приложения и т.д. В разных случаях работу с прокси решают по разному, какое-то время назад был придуман хороший проект под названием Browsermob-proxy, который очень помогает при автоматизации.

David Burns написал клиент на python для использования прокси с помощью python и даже приложил пример его использования для FirefoxDriver.

from browsermobproxy import Server
server = Server("path/to/browsermob-proxy")
server.start()
proxy = server.create_proxy()

from selenium import webdriver
profile  = webdriver.FirefoxProfile()
profile.set_proxy(proxy.selenium_proxy())
driver = webdriver.Firefox(firefox_profile=profile)

proxy.new_har("google")
driver.get("http://www.google.co.uk")
proxy.har # returns a HAR JSON blob

server.stop()
driver.quit()

Но вот для ChromeDriver примера нет, даже если хорошо погуглить. И люди, которые хотят использовать ChromeDriver, не знают, как заставить цепочку ChromeDriver + Python + Browsermob proxy работать. Потому, что не знают, как правильно подключать прокси к ChromeDriver.

Заполняю этот пробел c соответствующим примером кода:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException, 
ElementNotVisibleException
from browsermobproxy import Server
import urlparse

server = Server(r"c:\browsermob\bin\browsermob-proxy.bat")
server.start()
proxy = server.create_proxy()
proxy.new_har()

chrome_options = webdriver.ChromeOptions()
url = urlparse.urlparse(proxy.proxy).path
chrome_options.add_argument('--proxy-server=%s' % url)
driver = webdriver.Chrome(
	executable_path=r"c:\chromedriver.exe", 
	chrome_options=chrome_options)
driver.get("http://google.com.ua/")
driver.find_element_by_id("gbqfsb").click()

print proxy.har

driver.quit()
server.stop()

Детальных шагов не расписывал. Будут вопросы, пишите!

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.