背景
现在两个测试用例,一个登录成功的,一个登录失败的,经过之前引入的测试夹具,它们只剩下三个步骤了,一个是访问登录页面,这个两者都是相同的,都是访问同一个url;另一个则是定位元素+元素操作(假设login_error_01中定位了用户名、密码输入框,然后sendkeys为空 ),它们的定位方法、元素表达式都是相同的,都是定位的用户名、密码输入框;最后一个是断言,但是这两个用例用来断言的预期结果和实际结果都是完全不同的。
那么分析到这,我们有没有什么办法能再进行一层封装呢?我们可以发现,这两个测试用例都是在同一个页面下进行的,都是登录页面,而且定位的元素都是一样的,只是sendkeys的内容不同。这样我们可以把登录的操作全部封装在一个类里面,或用几个函数封装起来,单独写成一个模块,然后再在两个测试用例中进行调用。这样一来,我们的访问登录页面及登录的相关操作写到了一个模块里,测试相关的代码(断言)写到了test_login这个模块里,从而实现了页面操作及测试代码的解耦。
什么是PageObject?
PageObject,页面对象,也就是把页面封装成一个对象。像上面所说的登录相关的元素及操作,我们就可以封装成一个login_page的对象。那么既然是对象,它就有属性和方法,那PageObject有些什么属性和方法呢?
- 属性:元素定位器、url、网页标题
- 方法:定位元素、页面操作
这么一看是不是有点像DOM呢?DOM也是把整个html封装成一个对象,这个对象的属性是html中的元素及文本等,方法是元素的相关操作等,只不过我们这个PageObject范围更小而已,它只针对某个页面进行封装,而DOM是对整个浏览器进行封装。那么封装的目的是什么呢?当然是为了给外部进行调用。DOM是为了给编程语言进行调用,调用的目的是为了对浏览器进行操作;而PO是为了给测试用例进行调用,而调用PO的目的就是为了获取actual_result,从而可以用来跟expected_result进行断言。
PO的好处
PO最本质的目的是为了实现页面操作及测试操作的分离。那么除了这个最主要的好处之外,还有什么其他的好处呢?
- 可维护性。当前端改了页面上某一块东西,比如元素的一些属性,如果像以前我们把页面所有操作都写到测试用例中,那么如果有10个测试用例,都包含这个元素,我们就要每个测试用例都改一遍,非常麻烦。而封装成类之后,我们只需要改一个地方就行了。
- 可读性。这里我们可以拿封装PO前及封装PO后的测试用例方法来进行对比:
封装前:
def test_login_success(self,test_data,driver):
print(test_data)
"""
1.访问登录页面
2.元素定位、元素操作(定位用户名、密码并输入,点击登录)
3.通过页面返回结果与预期结果进行断言
"""
# 访问登录页面
url = "http://8.129.91.152:8765/Index/login.html"
driver.get(url)
#元素定位、元素操作(定位用户名、密码并输入,点击登录)
driver.find_element_by_name("phone").send_keys("18684720553")
driver.find_element_by_name("password").send_keys("python")
driver.find_element_by_class_name("btn-special").click()
# 通过页面返回结果与预期结果进行断言
actual_result = driver.find_element_by_xpath('//a[@href="/Member/index.html"]').text
assert actual_result in "我的帐户[python]"
封装后:
def test_login_success(self, test_info, driver):
"""登录成功用例"""
login_page = LoginPage(driver)
actual = login_page.get().login_success(
username=test_info["username"],
password=test_info["password"],
).get_account_name()
try:
assert actual == test_info["expected"]
except AssertionError as e:
Handler.logger.error("测试用例不通过")
raise e
如果我们要给另一个开发看代码,他看封装前的代码,需要一个个元素定位、元素操作的代码都看一遍才知道是在干嘛,而如果看封装后的代码就知道这是调用一个PO对象,而调用PO对象的目的就是为了获取实际结果,这样看代码就会非常有效率,而且非常容易理解。
- 可扩展性。如果这时前端在登录页面需要实现一个忘记密码的功能,而我们自动化中需要测试这个功能的话,我们只需要写在PO类中就行了,不用像封装前在各个测试用例中都要写一遍,非常麻烦。
- 可复用性。这个好处自然是不言而喻的,封装的好处就是在想用的时候可以随时调用。
代码实战
初步页面封装
login_page
我们可以把测试用例方法中的两步:(1)访问登录页面;(2)元素定位、元素操作(定位登录按钮,点击登录)都统一封装到login_page类中:
class Login_page():
def __init__(self,driver):
self.driver = driver
def login(self,username,password):
# 访问登录页面
url = "http://8.129.91.152:8765/Index/login.html"
self.driver.get(url)
# 元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.driver.find_element_by_name("phone").send_keys(username)
self.driver.find_element_by_name("password").send_keys(password)
self.driver.find_element_by_class_name("btn-special").click()
测试用例方法
在把初始化浏览器并打开这一步放到conftest,以及把两步:(1)访问登录页面;(2)元素定位、元素操作(定位登录按钮,点击登录)都统一封装到login_page类中后,测试用例的方法现在要做的事就很清晰,只有三步:
- 第一步:初始化测试页面。传入conftest中的driver对象,从而可以访问测试的页面;
- 第二步:调用页面逻辑进行页面操作。
- 第三步:通过页面返回结果与预期结果进行断言。
代码如下:
import unittest
from selenium import webdriver
from config import config
from middleware.handler import Handler
import pytest
from middleware.page.login_page import Login_page
#通过handler读取excel数据
login_error_data = Handler.excel.get_data("login_error")
class Test_login():
#用户名、密码为空
@pytest.mark.parametrize("test_data",login_error_data)
@pytest.mark.login_error_01
def test_login_error_01(self,test_data,driver):
#初始化页面
login_page = Login_page(driver)
#调用页面逻辑进行页面操作
login_page.login(
username=eval(test_data["数据"])["username"],
password=eval(test_data["数据"])["password"]
)
#通过页面返回结果与预期结果进行断言
actual_result = driver.find_element_by_xpath('//div[@class="form-error-info"]').text
assert "请输入手机号" in actual_result
封装元素操作
在登录页面逻辑中有三段元素操作的代码:
# 元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.driver.find_element_by_name("phone").send_keys(username)
self.driver.find_element_by_name("password").send_keys(password)
self.driver.find_element_by_class_name("btn-special").click()
那么这些元素操作需不需要再进一步封装呢?答案是:根据实际情况决定要不要进行封装。假设有A、B、C三个输入框,A是必填的,B、C有时需要填有时可不填,那么可封装B、C这两个输入框。另外单独封装元素操作方法的好处是,当元素表达式或定位元素的方法需要改动时,则只需要改一个地方就行了,不需要改多处。虽然现在这段代码看起来也只需要改一处,但指不定后面指不定会出现有多个场景需要同一个元素进行操作的情况呢。
class Login_page():
def __init__(self,driver):
self.driver = driver
def login(self,username,password):
# 访问登录页面
url = "http://8.129.91.152:8765/Index/login.html"
self.driver.get(url)
# 元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.enter_username(username)
self.enter_password(password)
self.click_login_button()
#输入用户名
def enter_username(self,username):
self.driver.find_element_by_name("phone").send_keys(username)
#输入密码
def enter_password(self,password):
self.driver.find_element_by_name("password").send_keys(password)
#点击登录按钮
def click_login_button(self):
self.driver.find_element_by_class_name("btn-special").click()
这么封装下来,明显可以发现,原先3行能解决的代码,却整整翻了三倍,变成了9行!但是日后总会看到好处的。
访问页面封装
我们接着看下登录的页面逻辑封装:
def login(self,username,password):
# 访问登录页面
url = "http://8.129.91.152:8765/Index/login.html"
self.driver.get(url)
# 元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.enter_username(username)
self.enter_password(password)
self.click_login_button()
这里面有访问url的代码,其实是可以单独抽离出来的。因为我们只在这个方法中封装元素定位+元素操作的代码,访问url并不属于我们这个方法里面的逻辑,所以应该单独抽离出来进行封装。
from config import config
class Login_page():
def __init__(self,driver):
self.driver = driver
host = config.HOST
url = host + "/Index/login.html"
def login(self,username,password):
# 元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.enter_username(username)
self.enter_password(password)
self.click_login_button()
#访问登录页面
def get_url(self):
self.driver.get(self.url)
#输入用户名
def enter_username(self,username):
self.driver.find_element_by_name("phone").send_keys(username)
#输入密码
def enter_password(self,password):
self.driver.find_element_by_name("password").send_keys(password)
#点击登录按钮
def click_login_button(self):
self.driver.find_element_by_class_name("btn-special").click()
链式调用引入
到了测试用例方法这边,如果我们要完成第二个步骤:调用页面逻辑进行页面操作,就需要调用两个方法:一个是get_url,另一个是login
#用户名、密码为空
@pytest.mark.parametrize("test_data",login_error_data)
@pytest.mark.login_error_01
def test_login_error_01(self,test_data,driver):
#调用login_page进行页面操作
Login_page(driver).get_url().login(
username=eval(test_data["数据"])["username"],
password=eval(test_data["数据"])["password"]
)
#通过页面返回结果与预期结果进行断言
actual_result = driver.find_element_by_xpath('//div[@class="form-error-info"]').text
assert "请输入手机号" in actual_result
但是执行的时候报了个错:
可以看到错误的原因是: AttributeError: ‘NoneType’ object has no attribute 'login’这是因为get_url这个对象中没有login这个方法,究其原因就是因为get_url方法没有返回self,所以不能继续调用login。
我们在login_page中的get_url方法中添加返回值,返回个self:
#访问登录页面
def get_url(self):
self.driver.get(self.url)
return self
这样就能正常运行了。
像测试用例方法这样需要连续调用页面逻辑中的多个方法,就可以使用我们之前说的链式调用。
在这里总结下页面操作返回值应该返回什么:
- 封装的页面操作的返回值
- 返回self。如果当前操作未跳转至另一个页面,还是在当前页面中,就返回self;
- 返回其他的页面对象。如果当前操作跳转至另一个页面,要在另一个页面执行另外的操作,就返回其他的页面对象;
- 返回元素对象或属性。如果需要获取某个元素或者属性,就直接返回元素本身或者属性。
- 根据结果封装多个方法。如果一个操作它可能会有多个结果,比如要进行页面跳转,然后在本页面进行操作,则需要根据结果封装多个方法。
实际结果的封装
我们看下测试用例方法中的获取实际结果的代码:
可看到这里也属于登录的页面中的操作,而我们把登录页面的操作逻辑与测试代码逻辑混起来了,这显然不符合我们上面所说的PO思想,说明封装得还不够彻底!
我们再切回login_page中进行封装:
#用户名、密码为空时获取错误信息
def get_error_message(self):
error_text = self.driver.find_element_by_xpath('//div[@class="form-error-info"]').text
return error_text
前面的login方法我们要返回self,不然到了测试方法中无法进行链式调用:
def login(self,username,password):
# 元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.enter_username(username)
self.enter_password(password)
self.click_login_button()
return self
接着返回测试方法中。这里可以直接调用login_page中的get_error_message来获取实际结果吗?很显然是不行的,因为get_error_message中返回的错误信息是建立在前面操作(访问url;输入用户名、密码,点击登录按钮)的基础上的,如果没输入用户名、密码点击登录按钮,那错误信息是不会出来的,也就定位不到。所以需要一系列方法的链式调用:Login_page().get_url().login().get_error_message()
代码如下:
#用户名、密码为空
@pytest.mark.parametrize("test_data",login_error_data)
@pytest.mark.login_error_01
def test_login_error_01(self,test_data,driver):
#调用login_page进行页面操作
actual_result = Login_page(driver).get_url().login(
username=eval(test_data["数据"])["username"],
password=eval(test_data["数据"])["password"]
).get_error_message()
assert "请输入手机号" in actual_result
多个测试用例引入
登录测试用例的引入
上面我们只写了一个登录时用户名、密码为空的测试用例,那么引入其他场景的测试用例会怎么样呢?比如说这里要引入登录成功的用例。
我们在测试类中把登录成功的测试方法给补上:
#登录成功
@pytest.mark.login_success
@pytest.mark.parametrize("test_data", login_success_data)
def test_login_success(self,test_data,driver):
# 调用login_page进行页面操作
Login_page(driver).get_url().login(
username=eval(test_data["数据"])["username"],
password=eval(test_data["数据"])["password"]
)
# 通过页面返回结果与预期结果进行断言
actual_result = driver.find_element_by_xpath('//a[@href="/Member/index.html"]').text
assert actual_result in "我的帐户[python]"
可以看到最后也执行成功了:
我们分析下登录的操作逻辑。我们先输入用户名、密码,然后点击登录按钮,接着会跳转至首页,我们再拿首页的元素进行断言。这里的actual_result定位的其实是首页页面的元素,并非登录页面的元素。而按照之前所说的PO思想:测试代码逻辑中不应直接混入页面操作代码逻辑,所以我们需要把这里的实际结果在页面逻辑中进行封装,然后在测试方法中调用。那这个获取实际结果的方法应该放到登录页面逻辑中吗?很明显不适合,因为输入用户名、密码点击登录之后,是跳转至首页页面,获取的实际结果也是首页中的。所以我们需要再封装一个页面:
- index_page.py
class Index_page():
def __init__(self,driver):
self.driver = driver
#获取登录成功后的登录信息
def get_login_message(self):
login_success_text = self.driver.find_element_by_xpath('//a[@href="/Member/index.html"]').text
return login_success_text
这样一来就有了两个页面逻辑:登录页面和首页页面。那么如何从登录页面跳转至首页页面呢?测试方法中如何进行调用呢?
测试方法中调用其实很简单,只需要再导入Index_page类,然后直接调用get_login_message即可:
#登录成功
@pytest.mark.login_success
@pytest.mark.parametrize("test_data", login_success_data)
def test_login_success(self,test_data,driver):
# 调用login_page进行页面操作
Login_page(driver).get_url().login(
username=eval(test_data["数据"])["username"],
password=eval(test_data["数据"])["password"]
)
# 通过页面返回结果与预期结果进行断言
from middleware.page.index_page import Index_page
actual_result = Index_page(driver).get_login_message()
assert actual_result in "我的帐户[python]"
这样显然是可以的,可以明显看出来调用了两个页面逻辑。我们可不可以接着上面登录页面逻辑的链式调用再调用首页页面逻辑的获取登录信息的方法呢?这就需要把login_page中的login进行拆分了。因为login中返回的是self,而self表示Login_page这个类,并不能调用Index_page类中的方法。所以得再拆分出一个login_success方法来返回Index_page对象:
#登录失败
def login_failed(self,username,password):
# 元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.enter_username(username)
self.enter_password(password)
self.click_login_button()
return self
#登录成功
def login_success(self,username,password):
# 元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.enter_username(username)
self.enter_password(password)
self.click_login_button()
return Index_page(self.driver)
可以看到这两个方法除了返回值不一样之外,其余代码都是相同的。而返回结果的不同就决定了要封装出两个方法(见上面所说的封装的页面操作的返回值的第四点)
返回到测试方法,这时就可以直接通过登录PO的链式调用来获取首页PO的登录信息了:
#登录成功
@pytest.mark.login_success
@pytest.mark.parametrize("test_data", login_success_data)
def test_login_success(self,test_data,driver):
# 调用login_page进行页面操作
actual_result = Login_page(driver).get_url().login_success(
username=eval(test_data["数据"])["username"],
password=eval(test_data["数据"])["password"]
).get_login_message()
assert actual_result in "我的帐户[python]"
测试数据分组
在引入登录成功的测试用例时,我们是把excel的数据进行了分组的:
然后在测试模块中用不同的变量接收,一个变量接收成功登录的测试数据,另一个变量接收登录失败的测试数据,然后分别传给对应的测试方法:
#通过handler读取excel数据
login_error_data = Handler.excel.get_data("login_error")
login_success_data = Handler.excel.get_data("login_success")
class Test_login():
#登录成功
@pytest.mark.login_success
@pytest.mark.parametrize("test_data", login_success_data)
def test_login_success(self,test_data,driver):
pass
#用户名、密码为空
@pytest.mark.parametrize("test_data",login_error_data)
@pytest.mark.login_error_01
def test_login_error_01(self,test_data,driver):
pass
之所以对测试数据进行分组,是因为上面所说的,登录成功和登录失败的操作步骤及操作所在页面不一样,决定了所用到的数据不一样。如果所有的excel数据都丢给某一个测试方法,如登录成功的方法,它既要读入登录成功的数据,又要读入操作失败的数据,显然是不合理的。
除了上述这种在excel中分开两个表单的方式进行分组之外,还有没有别的办法进行测试分组呢?
excel
excel除了可以进行表单分组来对数据进行分组外,还可以根据标题的关键字对数据进行分类。比如,如果标题中含有登录成功的,就把数据放到login_success_data中;如果标题中含有登录失败的,就把数据放到login_failed_data中。然后login_success_data传给登录成功的方法,login_failed_data传给登录失败的方法。
其实测试数据的关键还是数据和预期结果这两个字段,其他的内容可有可无。比如:测试步骤其实最后都会转化成代码,所以可以直接在代码中加上注释;标题只是方便给其他人看的,其实测试方法上方也可以通过注释来展示标题。所以我们可以不必用excel管理数据,完全把数据跟预期结果放到python当中,比如用变量封装,或写到配置文件中。
变量封装
这里我们可以直接把数据跟预期结果用变量封装起来,单独放到login_data.py模块中
"""封装登录操作所需要用到的数据"""
#登录成功数据
login_success = [
{"username":"18684720553", "password": "python","expected":"我的帐户[python]"}
]
#登录失败数据
"""
包括:
1.用户名为空,密码为空
2.输入正确用户名,密码为空
3.用户名为空,输入密码
"""
login_failed = [
{"username":"", "password": "","expected":"请输入手机号"},
{"username":"18684720553", "password": "","expected":"请输入密码"},
{"username":"", "password": "python","expected":"请输入手机号"}
]
到测试模块中,我们可以直接导入login_data里面的变量,login_success的数据传给登录成功的测试方法;login_failed传给登录失败的方法:
import unittest
from selenium import webdriver
from config import config
from data.login_data import login_success,login_failed
import pytest
from middleware.page.login_page import Login_page
class Test_login():
#登录成功
@pytest.mark.login_success
@pytest.mark.parametrize("test_data", login_success)
def test_login_success(self,test_data,driver):
# 调用login_page进行页面操作
actual_result = Login_page(driver).get_url().login_success(
username=test_data["username"],
password=test_data["password"]
).get_login_message()
assert actual_result in test_data["expected"]
#登录失败
@pytest.mark.parametrize("test_data",login_failed)
@pytest.mark.login_error
def test_login_error_01(self,test_data,driver):
#调用login_page进行页面操作
actual_result = Login_page(driver).get_url().login_failed(
username=test_data["username"],
password=test_data["password"]
).get_error_message()
assert test_data["expected"] in actual_result
写到配置文件中
我们还可以把数据及预期结果写到yaml配置文件当中:
#登录成功
login_success:
-
username: "18684720553"
password: "python"
expected: "我的帐户[python]"
#登录失败
login_failed:
-
username: ""
password: ""
expected: "请输入手机号"
-
username: "18684720553"
password: ""
expected: "请输入密码"
-
username: ""
password: "python"
expected: "请输入手机号"
在测试模块中,通过handler中的yaml_data读取出来:
import unittest
from selenium import webdriver
from config import config
from middleware.handler import Handler
import pytest
from middleware.page.login_page import Login_page
login_success = Handler.yaml_data["login_success"]
login_failed = Handler.yaml_data["login_failed"]
class Test_login():
#登录成功
@pytest.mark.login_success
@pytest.mark.parametrize("test_data", login_success)
def test_login_success(self,test_data,driver):
# 调用login_page进行页面操作
actual_result = Login_page(driver).get_url().login_success(
username=test_data["username"],
password=test_data["password"]
).get_login_message()
assert actual_result in test_data["expected"]
#用户名、密码为空
@pytest.mark.parametrize("test_data",login_failed)
@pytest.mark.login_error_01
def test_login_error_01(self,test_data,driver):
#调用login_page进行页面操作
actual_result = Login_page(driver).get_url().login_failed(
username=test_data["username"],
password=test_data["password"]
).get_error_message()
assert test_data["expected"] in actual_result
元素表达式封装
我们回过头来看下login_page中封装的元素操作的方法:
#访问登录页面
def get_url(self):
self.driver.get(self.url)
return self
#输入用户名
def enter_username(self,username):
self.driver.find_element_by_name("phone").send_keys(username)
#输入密码
def enter_password(self,password):
self.driver.find_element_by_name("password").send_keys(password)
#点击登录按钮
def click_login_button(self):
self.driver.find_element_by_class_name("btn-special").click()
#用户名、密码为空时获取错误信息
def get_error_message(self):
error_text = self.driver.find_element_by_xpath('//div[@class="form-error-info"]').text
return error_text
元素定位(find_elementXXX)其实也是可以进行封装的。为什么就这么一点代码都要封装呢?其实封装这一块内容只有一个好处:方便维护。如果元素定位需要修改,我们只需要修改封装元素定位的地方就ok了。
那么该怎么封装呢?我们可以用字典、元祖、列表的形式存储元素定位的定位方法及定位表达式,然后通过find_element方法传入by(通过什么方式定位)及value(元素定位表达式)。
封装的元素定位如下:
#元素定位(定位方法+定位表达式)
user_locator = {"by":"name","value":"phone"}
pwd_locator = {"by":"name","value":"password"}
login_btn_locator = {"by":"class_name","value":"btn-special"}
error_text_locator = {"by":"xpath","value":'//div[@class="form-error-info"]'}
接着在定位方法中,通过find_element方法传入各自的变量(得先进行拆包。元素、列表需加一个星进行拆包;字典需加2个星):
#输入用户名
def enter_username(self,username):
self.driver.find_element(**self.user_locator).send_keys(username)
#输入密码
def enter_password(self,password):
self.driver.find_element(**self.pwd_locator).send_keys(password)
#点击登录按钮
def click_login_button(self):
self.driver.find_element(**self.login_btn_locator).click()
#用户名、密码为空时获取错误信息
def get_error_message(self):
error_text = self.driver.find_element(**self.error_text_locator).text
return error_text
但是实际执行时却报了个错,意思是class_name这个定位的方式selenium是不支持的:
接着我们看回封装的元素定位,登录按钮的定位用到了class_name的定位方法。selenium不是支持class_name的定位吗?为什么这里就不行了?
这里我们就需要看回find_element的源码了:
可以看到的是,这里by只是一个变量,它接收的是By这个类下面属性的值。接着我们定位到By类里面看下源码:
可以看到CLASS_NAME的变量值为class name,而我们写成了class_name,自然是定位不到的。
改成class name后,就能正常执行了:
但是直接这样写by的变量值然后传给by很容易出现问题,就像刚刚那样。所以我们可以引入By这个类,直接调用里面的变量再传给by。
通过进入By类所在的模块,我们可以看到这个类所在的路径:
修改成通过By类调用变量的封装如下:
#元素定位(定位方法+定位表达式)
from selenium.webdriver.common.by import By
user_locator = {"by":By.NAME,"value":"phone"}
pwd_locator = {"by":By.NAME,"value":"password"}
login_btn_locator = {"by":By.CLASS_NAME,"value":"btn-special"}
error_text_locator = {"by":By.XPATH,"value":'//div[@class="form-error-info"]'}
测试用例方法中的简单拆分
现在测试方法是这样的:
#登录成功
@pytest.mark.login_success
@pytest.mark.parametrize("test_data", login_success)
def test_login_success(self,test_data,driver):
# 调用login_page进行页面操作
actual_result = Login_page(driver).get_url().login_success(
username=test_data["username"],
password=test_data["password"]
).get_login_message()
assert actual_result in test_data["expected"]
我们要进行简单的拆分,分成测试方法通用的三步:
- 初始化页面
- 调用页面行为完成页面操作,从而获取实际结果
- 实际结果与预期结果的断言
整理后的代码如下:
#登录成功
@pytest.mark.login_success
@pytest.mark.parametrize("test_data", login_success)
def test_login_success(self,test_data,driver):
# 初始化页面
login_page = Login_page(driver)
# 调用页面行为完成页面操作,从而获取实际结果
actual_result = login_page.get_url().login_success(
username=test_data["username"],
password=test_data["password"]
).get_login_message()
# 实际结果与预期结果的断言
assert actual_result in test_data["expected"]
日志的引入
最后只剩下最后一个地方了:日志的引入。当断言失败时,我们需要抛出异常日志:
# 实际结果与预期结果的断言
try:
assert actual_result in test_data["expected"]
except AssertionError as e:
Handler.logger.error("测试用例不通过")
raise e
web自动化测试用例实现的流程
- 页面场景分析。先在页面进行一遍手工测试的操作,并分析有哪些测试步骤(要写清楚在哪个页面,执行什么操作),顺便进行元素定位。
- 准备前置后置。前置、后置写到专门的测试夹具模块:conftest当中。
- 根据测试步骤封装页面行为。根据测试步骤要用到的元素定位、元素操作、获取断言的实际结果等写到PO中。
- 调用页面行为,获取实际结果。在测试用例方法中调用刚刚封装好的PO,获取实际结果。
- 断言。实际结果与预期结果相比进行断言。
综上所述,一个完整的测试用例方法如下:
@pytest.mark.parametrize("test_info", login_invalid)
def test_login_invalid(self, test_info, driver):
"""登录未授权.
测试步骤:
1, 登录页面输入用户名
2, 登录页面输入密码
3, 登录页面点击登录
4, 登录页面获取未授权信息
"""
login_page = LoginPage(driver)
actual = login_page.get().login_fail(
username=test_info["username"],
password=test_info["password"]
).get_invalid_message()
try:
assert actual == test_info["expected"]
except AssertionError as e:
Handler.logger.error("测试用例不通过")
raise e