前言
pytest是非常成熟、功能强大的测试框架。具有非常多的优点:
- 简单灵活,容易上手,支持参数化
- 自动发现测试模块和测试方法
- 断言使用assert + 表达式即可
- 可以设置测试会话级、模块级、类级、函数级的fixtures。
- 共享前置、后置
- 具有丰富的插件库,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、pytest-xdist(多CPU分发)等
- 用例分标签执行
- 很好的集成Jenkins
等等!!!
安装
pytest需要安装:
pip install pytest
安装完成后,验证安装:
pytest --version
命名规则
刚才在前言中提到,pytest可以自动搜索用例并执行,但是,其实它在搜索用例的时候,有一定的规则:
- 单测的文件,命名符合test*.py 或者 *_test.py。
- 以test开头的函数。
- 以Test开头的测试类(没有__init__函数)当中,以test_开头的函数。
标签
- 标签注册
在给用例打标签时,首先要注册标签。注册标签的方式有多种,这里只说明第一种:
首先创建pytest.ini文件,在文件中按如下形式添加标签名:
import pytest
[pytest]
marks=slow:marks tests as slow (delete with '-m "not slow"')
serial
上面注册了一个标签名为slow的标签。注意:冒号后是【可选的】描述信息,只能用英文,用中文可能会出现一些问题。
当注册完标签名之后,就可以给用例、测试类、测试模块打标签:@pytest.mark.标签名称。
给测试用例打标签,在测试用例函数前:
@pytest.mark.demo
def test_demo3():
assert 100 == 100
给测试类打标签,写在测试类之前,当给测试类打标签时,类里所有以test开头的函数都拥有此标签,并且还可以再单独给类里的某一个函数打标签,这样,类里的函数即具备类的标签,也有自己的标签:
# @pytest.mark.test
class TestDemo:
@pytest.mark.demo
def test_demo(self):
assert 1001 == 1001
给模块打标签时,在导入的模块之后,所有的用例之前:
import pytest
@pytest.mark.smoke
def test_demo3():
assert 100 == 100
class TestDemo:
def test_demo(self):
assert 1001 == 1001
def test_demo1(self):
assert 1 == 1
还可以打多个标签:
@pytest.mark.smoke
@pytest.mark.demo
def test_demo3():
assert 100 == 100
除了上面的打标签的方式,还有另外一种,比如下面给函数打标签,则写在函数体内:
class TestDemo:
# 第二种打标签的方式
pytestmark = pytest.mark.demo
# 多个标签
# pytestmark = [pytest.mark.demo, pytest.mark.test]
def test_demo(self):
assert 1001 == 1001
def test_demo1(self):
assert 1 == 1
当我们给用例、类、模块打上标签之后,就可以根据标签去筛选、运行用例。具体执行方式,在下面运行章节中。
fixture
fixture修饰器来标记固定的工厂函数。在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于共享前置、后置,或者其他重复的工作。
测试函数可以直接使用fixture函数的名称作为输入参数,将从fixture函数返回参数传入测试函数。
fixture函数可以使用 return 或 yield 语句将它们的值提供给测试函数。 使用 yield 时,yield在使用时,会起到分割和返回的作用。
在其他模块中,使用fixture时,不需要导入,在运行测试之前调用:可以使用 pytest.mark.usefixtures(fixturename) 标记。
创建conftest.py文件,注意、注意、注意,名称不可修改为其他,只能是conftest.py。
在conftest.py文件中,编写fixture的函数。
fixture方法:
fixture(scope="function", params=None, autouse=False, ids=None, name=None)
- scope:被标记方法的作用域
-function (default):作用于每个测试方法,每个test都运行一次
-class:作用于整个类,类中所有用例之前,执行一次
-module:作用于整个模块,每个module的所有test只运行一次
-session:作用于整个session(慎用),每个会话只运行一次 - params:(list类型)提供参数数据,供调用标记方法的函数使用
- autouse:是否自动运行,默认为False不运行,设置为True自动运行(一般不要设置为Ture,谨慎开启)
示例:
@pytest.fixture(scope="function")
def init_driver():
# 前置:打开谷歌浏览器,访问课堂派并登录
driver = webdriver.Chrome()
driver.maximize_window()
driver.get(cd.login_url)
lp = LoginPage(driver)
# 分割线 +返回值
# 分割线:当yield前面的代码执行完成之后,执行测试用例,用例执行完,再执行yield之后的代码
# 返回值:yield之后跟的是返回值,返回的是元组
yield driver, lp
# 后置:关闭浏览器
driver.quit()
当我们在测试中引用时:
@pytest.mark.usefixtures("init_driver") # 调用函数,init_driver就是我们上面的定义的fixture函数名称
class TestLogin:
def test_login_success(self,init_driver): # 传入的init_driver 就是调用init_driver函数返回的值
init_driver[1].login(ld.normal_datas['user'], ld.normal_datas['passwd']) # init_driver[1]就是返回的lp,LoginPage的对象
assert HomePage(init_driver[0]).if_user_is_exist() # init_driver[0]函数返回的是driver,浏览器对象
fixture函数还可以继承。
- 同级继承时,比如下面test2继承test1时,都是class级别的,其他模块中调用test2,执行时,会先执行test1的前置,然后执行test2的前置,之后去执行用例,用例执行完成后,去执行test2的后置,然后再执行test1的后置:
@pytest.fixture(scope="class")
def test1():
# 前置:打开谷歌浏览器,访问**并登录
driver = webdriver.Chrome()
driver.maximize_window()
driver.get(cd.login_url)
lp = LoginPage(driver)
# 1.分割线 +返回值
yield driver, lp
# 后置:关闭浏览器
driver.quit()
# 继承test1
@pytest.fixture(scope="class")
def test2(test1):
# 继承test1的前置和后置,作为函数参数,函数名就是返回值
LoginPage(test1[0]).login(*cd.student_datas) # 传入test1[0],即test1返回的driver
yield test1
- 不同继承作用域比自己大的函数。比如下面的get_home作用域为测试用例,init_course作用域为类,get_home继承init_course,那么在调用get_home时,会先执行login_web的前置,执行一次,每个用例执行完之后,会执行一次get_home后置,所有用例执行完成之后,执行一次login_web的后置:
@pytest.fixture(scope="class")
def login_web(init_course):
# 前置:打开谷歌浏览器,访问**并登录
driver = webdriver.Chrome()
driver.maximize_window()
driver.get(cd.login_url)
# 以学生账户登录
LoginPage(driver).login(*cd.student_datas)
cp = CoursePage(driver)
yield driver, cp
driver.quit()
# 继承类的前置,执行时,会执行类的前置,然后每一个用例会执行get_home的后置
@pytest.fixture(scope="function")
def get_home(login_web):
yield login_web
login_web[0].get(cd.login_url)
运行
cmd命令运行:
使用cmd命令运行时,要注意cmd的目录,因为不指定目录时,默认在那个路径下,就去那个路径下寻找用例。
pycharm中terminal中通过命令运行:
这个其实和在cmd命令中一样。
执行指定标记的用例:
pytest -m smoke # 执行标记为smoke的用例
指定测试模块:
pytest test_001.py # 测试test_001.py文件中的用例
指定测试目录:
pytest testcases/ # 测试testcases目录下的用例
关键字筛选执行:
pytest -k "TestClass and not method"
这条命令会匹配文件名、类名、方法名匹配表达式的用例,这里这条命令会运行 TestClass.test_smoke, 不会执行 TestClass.test_method_smoke
运行指定测试用例:
pytest test_001.py::test_add #执行test_001.py文件中的test_add函数
除了使用命令运行,还可以使用main函数运行:
import pytest
if __name__ == '__main__':
pytest.main() # 和命令行运行的形式一样
和命令行的运行是一样的。