Robotium+BlackBox+SlidingMenu http://t.co/v8LEA4cRMI
[Рецепт] Создание вложенных словарей для тестовых нужд без особых проблем, пример на Python
В поддержку новой инициативы 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
Проблема: в 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_
self.assertEquals(1, 2)
AssertionError: 1 != 2
==============================
FAIL: Test 5
——————————
Traceback (most recent call last):
File “/home/katerina/Desktop/my_
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_
self.assertEquals(1, 2)
AssertionError: 1 != 2
==============================
FAIL: Test 5
——————————
Traceback (most recent call last):
File “/home/katerina/Desktop/my_
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
Вопрос логирования очень часто возникает при автоматизации тестирования. Каждый кто автоматизировал больше одного теста знает, что логи это ключ к сэкономленным часам анализа ошибок и разборов поломанных тестов.
Как правильно выполнить логирование для Вашего проекта? Не знаю, каждый проект это отдельная история, которая должна рассматриваться в отдельности. (Обращайтесь, будем разбираться!) Но я хочу внести некоторые свои комментарии в данный процесс.
Меня попросили посмотреть на код и показать, как можно выводить документацию для каждого метода в лог. В 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)
Как всегда, пишите, если у Вас есть вопросы! Удачи и хорошего Вам питонирования!
ChromeDriver + Python + Browsermob proxy
Для бывалых автоматизаторов веб-приложений известно, как важно иметь под руками прокси сервер, с помощью которого можно решить целый ряд вопросов и проблем с веб-автоматизации. 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()
Детальных шагов не расписывал. Будут вопросы, пишите!
Python mock: добавление метода реального класса к mock объекту
Я думаю многим из нас приходилось работать с юнит тестированием, но немногим приходилось работать с разработкой моков. Для тех кто несильно знаком с этой концепцией могут почитать вводную статью.
Так вот, в python есть прекрасная библиотека, которая называется mock. Все было бы хорошо, но к сожалению, у нее нет встроенных средств добавления метода реального класса к уже созданному mock экземпляру класса. Зачем это нужно? (Возможно спросите вы)
Например, вы разрабатываете класс и какой-то метод для него. Вам необходимо протестировать только этот метод класса не вызывая остальных. Как это можно сделать? Вот примерно где-то так:
from mock import Mock import new class RealClass(object): def other_method(self): self.attribute_initiated_earlier = "class value" def real_method(self): return [self.attribute_initiated_earlier, 'password'] fake_object = Mock(spec=RealClass()) class_method = object.__getattribute__(RealClass, 'real_method') fake_object.real_method = new.instancemethod(class_method, fake_object, Mock) fake_object.attribute_initiated_earlier = "new value" print fake_object.real_method()