是的, 我这个java技术栈的测试人开始玩python了,公司产品向用户提供了python的sdk,所以为了能针对sdk做一些东西。我这两天研究了一下python和python通用的测试框架。一开始在社区和群里问了一些小伙伴们使用的都是什么,发现大多数人用的都是unittest+HTMLTestRunner。 可能是我被java界那庞大的测试生态圈惯坏了,第一天就已然受不了unittest+ HTMLTestRunner的这种组合。所谓工欲善其事必先利其器,我花了一点时间google了一下python界常用的一些测试框架,像unittest2,nose,pyunit,doctest什么的。 挨个去官网查看了一下使用方式。发现pytest还是比较符合我的期望的。而且我用java的时候就一直使用的report框架----allure 也支持了pytest。 所以今天还是老规矩。入门科普,抛砖引玉,详尽的文档请参考:http://doc.pytest.org/en/latest/contents.html 以及: https://github.com/allure-framework/allure-pytest。
pytest的执行方式非常简单, 它跟xunit 系的框架不一样,测试类不需要继承任何pytest的类。 在运行pytest 命令的时候,自然会有一套case discover的机制识别。只需要你的类是以也就是说你的测试类和方法可以跟普通方法是一样的。只需要你的测试文件的命名规则符合test_*.py 或者 *_test.py。 例子如下:
class TestDataLoad():
def test_data_load(self, me):
source_table = None
# 判断是否已经导入这个数据
bank_data = [t for t in me.tables if t.name == 'test_gaofei01']
if len(bank_data) != 0:
source_table = bank_data.pop()
else:
source_table = me.new_local_table('./banking_2_23.csv', 'test_gaofei01', format_='csv',
first_line_schema=False)
这个时候我们到目录下执行py.test。你会发现已经自动发现了你的测试函数并执行。 不需要像其他框架一样继承某一个类或者在函数上使用某个装饰器。 关于用例运行的具体配置,可以参照链接:http://doc.pytest.org/en/latest/usage.html
fixture 一词最早是在xunit test patterns 看到的。我也不知道翻译过来到底该叫什么,可以认为是测试所依赖的所有东西,例如参数,测试数据,数据库连接等。 在其他测试框架中一般提供了@setup @teardown 这类的方法。python中使用@pytest.fixture 这样一个装饰器。我们直接看一个最简单的例子。
class ProphetClientBase(object):
@pytest.fixture(scope='session')
@allure.step('登录')
def me(self):
client = ProphetClient('http://172.27.1.115:8888')
assert client.login('Admin', '1234567'), '登录失败'
return client.me
上面我们用了pytest提供的装饰器来创建我们的fixture。 这个方法也很简单,主要负责登录并返回用户对象。 这个用户对象是大部分case需要用的。 所以我们把它定义为一个fixture。在其他框架中我们用setup method的方式来做的,这样做有个缺点就是setup method无法传递数据给测试方法作为参数。但是pytest中我们可以像下面一样做:
def test_data_load(self, me):
source_table = None
# 判断是否已经导入这个数据
bank_data = [t for t in me.tables if t.name == 'test_gaofei01']
上面我们定义测试方法的时候直接在参数中使用me这个参数。pytest在运行的时候会自动的帮我们找到me这个fixture运行并将返回值赋值到me中。不用我们做其他的处理。 同样我们在fixture的定义中可以看到我们使用了scope='session', 意思是这个fixture是属于session级别的,在一个session中只会运行一次。是不是有点像testng的beforeTest系列了~
刚才说了before系列,现在我们说说after系列,也即是teardown。 一个fixture有时候是需要进行销毁操作的。 其他框架使用after或者teardown方法。 但是pytest可以直接在fixture中定义这个操作。只需要使用python的关键字yield 代替return。 并在yield之后编写销毁操作。 举个例子,还是上面的登录的fixture,现在我们这么写:
@pytest.fixture(scope='session')
@allure.step('登录')
def me(self):
client = ProphetClient('http://172.27.1.115:8888')
assert client.login('Admin', '1234567'), '登录失败'
yield client.me
client.me.logout()
上面我们用yield 代替return后执行了logout操作。 这样就好像是生成器一般在所有使用这个fixture的测试执行结束后。运行了logout操作。同样pytest 也支持with 。。 as大法来配合yield。如下:
@pytest.fixture
def passwd():
with open("/etc/passwd") as f:
yield f.readlines()
def test_has_lines(passwd):
assert len(passwd) >= 1
额,我是直接按字面直接翻译了。 其实这个功能就像是java的反射一样,可以在fixture中动态获取测试模块的属性来动态的执行。直接给个例子, 我们在一个文件中定义fixture
@pytest.fixture(scope="module")
def smtp(request):
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
smtp = smtplib.SMTP(server)
yield smtp
print ("finalizing %s (%s)" % (smtp, server))
smtp.close()
然后再另一个文件中定义测试模块
smtpserver = "mail.python.org" # will be read by smtp fixture
def test_showhelo(smtp):
assert 0, smtp.helo()
在上面的例子中我们在fixture中使用了request这个pytest提供的默认参数,它其中有一个功能是可以提供测试方法的运行环境(我理解这个功能很像java 反射的功能之一)。在fixture中我们通过getattr直接反射出了测试模块中一个叫smtpserver的属性。并使用这个属性启动server。
我们可以参数化fixture并且让测试函数根据不同的fixture运行多次测试(很像是数据驱动的一种实现)。例子如下:
@pytest.fixture(scope="module",
params=["smtp.gmail.com", "mail.python.org"])
def smtp(request):
smtp = smtplib.SMTP(request.param)
yield smtp
print ("finalizing %s" % smtp)
smtp.close()
很简单,直接使用params传入一个参数列表。然后再fixture中使用request.param使用当前的参数。 这样你会发现测试方法会使用这两个fixture执行两次。
除了在测试方法中显示的使用fixture函数作为参数外,我们还可以让fixture传递参数而自动执行。只需要使用@pytest.mark.usefixtures("cleandir") 这个方式就可以了。
@pytest.mark.usefixtures("cleandir")
class TestDirectoryInit:
def test_cwd_starts_empty(self):
assert os.listdir(os.getcwd()) == []
with open("myfile", "w") as f:
f.write("hello")
def test_cwd_again_starts_empty(self):
assert os.listdir(os.getcwd()) == []
上面的例子里在class的定义中使用这个装饰器。这样在运行这个class的测试方法前都会执行一次fixture方法。 好了关于fixture的主要就介绍这么多了。 详细的请看链接:http://doc.pytest.org/en/latest/fixture.html
刚才说了fixture的使用方式,但是还是无法满足我们的日常需要,我们希望有更灵活的方式给测试方法进行参数化。例如我们日常的数据驱动的测试策略。 pytest中同样给我们提供了这样的一个方式。看下面的例子:
import pytest
def value(xmlPath):
# 读取参数文件的代码
pa = [
("3+5", 8),
("2+4", 6),
("6*9", 54),
]
return pa
@pytest.mark.parametrize("test_input,expected", value(path))
def test_eval(test_input, expected):
print(test_input)
assert eval(test_input) == expected
上面是我为了实现我在testng中做数据驱动的功能而写的demo。通过parametrize这个装饰器我们就实现了跟testng中的data provider很相似的功能。tesng是接受一个2维数组,而pytest则是一个装有多个元祖的列表。这个我觉得可能不用多说了,知道数据驱动的同学都懂。
好了关于pytest我就先介绍到这吧,pytest是个很大的框架但是使用起来很简单。我今天只介绍了我们常用的比较重要的功能。 详细的文档请参阅:http://doc.pytest.org/en/latest/contents.html
下面来看看为了显示高大上的report,我们抛弃了HTMLTestRunner而直接使用allure-pytest。 我之前发过帖子专门说明了allure在java和jenkins上如何配置插件,详情请看:https://testerhome.com/topics/5738。 来我先安利一下效果图:
py.test --alluredir report
这样我们的测试报告就生成在report目录下了。跟java的标注不太一样,pytest使用装饰器的样子大概是这样的。