😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。
📡主页地址:【Austin_zhai】
🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。
💎声明:博主日常工作较为繁忙,文章会不定期更新,各类行业或职场问题欢迎大家私信,有空必回。
阅读目录
- 1. 目的
- 2. 编写的基础原则
- 2.1 用例的简洁、清晰
- 2.2 用例的可重复性
- 2.3 用例的覆盖率
- 2.4 确保测试用例可维护性
- 3. 用例编写技巧
- 3.1 步骤拆解
- 3.2 模块化
- 3.3 参数化
- 3.4 数据驱动
- 3.5 断言
- 3.5.1 assertAlmostEqual
- 3.5.2 assertRegex
1. 目的
web自动化测试作为软件自动化测试领域中绕不过去的一个“香饽饽”,通常都会作为广大测试从业者的首选学习对象,相较于C/S架构的自动化来说,B/S有着其无法忽视的诸多优势,从行业发展趋、研发模式特点、测试工具支持,其整体的完整生态已经远远超过了C/S架构方面的测试价值。
截止现在,我们已经学习了web自动化测试的大部分基础内容,有了之前的这些知识与实践,我们已经可以大致将一个web自动化脚本写出来了,那么在我们执行脚本之前,还有一个比较重要的部分,那就是自动化测试用例,这个可以说是我们整个测试用最最至关重要的部分了,没有了它自动化测试的基准也会变得混沌不清,所以如何写好自动化中的测试用例直接决定了你的自动化测试的落地价值与执行意义。
2. 编写的基础原则
我们在动手转化或编写自动化测试用例之前,需要先搞清楚几件事情,无论是从手工测试中转化还是重新设计,都推荐遵循以下几条原则,这些和我们的黑盒测试的设计原则相近。
2.1 用例的简洁、清晰
自动化测试用例的意图应该易于理解和解释。每个测试用例应该只关注一个特定的功能或场景,以确保测试结果的清晰和易于解释。这里的功能与场景可以理解为在自动化测试执行中需要验证的是单个功能点还是一组业务流程,这个和黑盒测试的测试用例有点区别,正是因为自动化测试的特性,可以帮助测试在极短的时间内对某个所需测试的业务流进行验证,所以使用测试用例来验证一些重复的业务场景正是自动化测试的价值体现。
2.2 用例的可重复性
自动化测试用例应该可以重复执行,这个与黑盒用例的设计理念一致,我们在日常的黑盒测试结束以后都会将测试用例补全或优化,方便之后的迭代版本再次复用或回归重组。那么在自动化测试用例中,用例的重复利用也就变的顺理成章了。当然,虽然理念相同,但实际设计与操作起来还是有些区别的,比如自动化测试用例在设计的时候要考虑测试数据的生成与回收。
2.3 用例的覆盖率
覆盖率永远是测试活动中的永恒课题之一,如何拥有良好的覆盖率也是广大测试人员的争论不休的话题,针对自动化测试,大家要清楚,覆盖率的认知与黑盒测试的不同,这里仍然是基于自动化测试的特性,覆盖的范围与覆盖率应当遵从各自团队自动化测试的需求与项目痛点来进行定义, 不能一概而论,比如A公司的自动化测试解决的是回归测试人手不足的问题,那么覆盖范围与覆盖率应集中在主业务的happy path上,过多的业务分支将会产生过多的自动化脚本与测试用例,同时也会间接的提升人员的时间成本与维护效率 。如果版本质量不佳,但人员较为充足,自动化的覆盖率与范围可以适当的提升,将一些常见的异常场景与次级重要的业务线加入其中,可以有效的降低人员测试失误或漏测的现象发生。
2.4 确保测试用例可维护性
自动化测试用例应该易于维护,这句话该如何理解呢?其实作为自动化测试脚本的一部分,测试用例的维护往往直接体现在它的设计结构,在我们编写自动化测试用例的时候,同样应当考虑后续的维护难易度,这里就比较推荐模块化的设计理念,用例之间不应该存在强耦合,直接的数据依赖等因素,这样会影响用例的模块化,较为良好的模块化测试用例可以有效提升后期的用例维护难度与降低用例重组、更新升级的难度。
3. 用例编写技巧
说了那么多,接下来就让我们来进入实际编写的环节吧,在编写的同时我们将逐步理解其中的一些编写技巧与操作理念。
3.1 步骤拆解
我们在编写自动化测试用例的时候,可以根据业务功能模块的操作特点将测试用例里的业务操作分割成多个步骤,每个步骤必须有明确的操作行为,如此我们就可以精确的控制测试用例执行时候的测试过程,也贴合了高可维护性的设计基础原则。
拿PO模式的UI测试框架来说,如果我们要测试一个登录页面,首先的元素定位操作大家都可以理解,那元素定位之后我们需要做什么呢?其实光有元素定位肯定是不够的,我们还需要对应的业务操作。业务操作的封装也是见仁见智,有的在PO中封装,有的在用例中直接操作。但这不影响我们今天要说的这个主题,在我们编写测试用例的时候,依照步骤拆解的技巧,我们可以把登录功能的验证点划分为:
✔️打开登录页面
✔️ 输入用户名
✔️ 输入密码
✔️ 点击登录按钮
✔️ 验证登录是否成功
如上可见,步骤拆解的目的就在于它将一个完整的测试用例业务操作拆成了多个测试步骤,我们可以单独对每一个步骤进行代码设计,最后将其组合程一个完整的用例,这样设计出的用例在阅读与维护方面就会有较大的提升。
3.2 模块化
模块化也是设计自动化测试用例中非常重要的一块内容,这个从根本上来说与面向对象的编程理念是相同的,说人话就是将一些需要经常用到的业务操作与功能模块进行模块化,以便后续的业务操作直接复用。比如我们日常中最常见的用户注册与登录、登出、某些模板的增删改查等功能。
3.3 参数化
参数化测试也是目前比较主流的一种用例设计方式,它的本质就是可以在测试用例中使用不同的参数进行多次执行测试。这对于相似的测试场景非常有用,可以大大简化测试用例的编写。也就相当于在一个测试用例中执行了多次,但每一次的参数都是不同的。对一些较大的边界值,输入类组件,条件较多的排列组合的场景特别的适用。
这里以unittest来举例说明,参数化在这个库中可以使用subTest()方法类进行实现。
import unittest
from selenium import webdriver
class LoginTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def tearDown(self):
self.driver.quit()
def test_login(self):
data = [
{'username': 'user1', 'mpd': 'pwdmpd1'},
{'username': 'user2', 'mpd': 'pwdmpd2'},
{'username': 'user3', 'mpd': 'pwdmpd3'},
]
for d in data:
with self.subTest(username=d['username'], password=d['password']):
self.driver.get('http://localhost/login')
self.driver.find_element_by_name('username').send_keys(d['username'])
self.driver.find_element_by_name('password').send_keys(d['password'])
self.driver.find_element_by_css_selector('button[type="submit"]').click()
# assert login success
self.assertTrue(self.driver.current_url.endswith('/dashboard'))
if __name__ == '__main__':
unittest.main()
在上面的代码中,我们使用了一个字典列表来保存不同的用户名和密码组合。然后使用for循环来遍历这个列表,并使用subTest()方法来为每组数据生成一个独立的测试用例。这样就可以在测试报告中看到每个数据的测试结果,方便排查问题。
3.4 数据驱动
数据驱动和之前的参数化是不同的两个概念,数据驱动是更加完整的数据测试的体现,它将测试数据和测试逻辑分离,将测试数据存储在外部数据源中,然后使用程序来读取测试数据并执行测试。这里的数据源可以是多种多样的,具体是文件还是持久化则根据各自公司的业务来决定。
同样的,我们可以使用pandas这个三方库来实现DDT,假设我们有这样一份数据文件,里面保存了被测对象的用户名和密码。
usern,psswd
u-test1,pwd1
u-test2,pwd2
u-test3,pwd3
然后,我们封装一个test_data_load函数,用来读取这个CSV文件,并将每行数据转换成一个字典对象。
这个函数会返回一个字典列表,每个字典对应一行数据,字典的key对应CSV文件的列名,value匹配对应列的值。
import pandas as pd
def test_data_load(file):
data = pd.read_csv(file)
return data.to_dict('records')
我们在测试用例中使用这个函数来读取测试数据,并使用for循环来遍历每个数据执行测试。
import unittest
from selenium import webdriver
from test_function import load_test_data
class LoginTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def tearDown(self):
self.driver.quit()
def test_login(self):
test_data = load_test_data('test.csv')
for data in test_data:
username = data['username']
password = data['password']
self.driver.get('https://www.test.com')
if __name__ == '__main__':
unittest.main()
3.5 断言
最后我们还是要来说一说断言,测试用例中作为判断测试结果是否符合预期结果的重要方式之一,我们有足够的理由在断言上下大功夫来设计此部分。今天介绍给大家的不单单是一些基础的断言方式,为了使我们的测试断言更加的完善与多样化,我们还需要利用断言的各种其他方法来进行更加精准的结果判断。
3.5.1 assertAlmostEqual
assertEqual,想必大家已经很熟悉了,一般的结果判断都会用此方法来断言。那么assertAlmostEqual又是什么呢?这个断言方法是用来对比浮点数对象是否大致相等的。为什么是大致呢?其实写过代码的同学应该都清楚,在代码中小数的位数不单单只是后两位这么简单,包括小数的计算也是较为复杂的,所以这里我们只需要运用这个特性,在需要对比的小数位的基础上多对比几位即可达到我们要的效果。
class TestMath(unittest.TestCase):
def test_divide(self):
result = 3.0 / 2
self.assertAlmostEqual(result, 1.5, places=3, msg='除法的结果不正确。')
我们来看下这个断言方法所带的参数的大致含义,第一个和第二个是要对比的数值,第三个places是一个可选参数,它是需要对比的小数位,默认为7,也就是对比到第七位。所以在不指定位数的情况下:
self.assertAlmostEqual(0.5, 0.4) return false
self.assertAlmostEqual(0.12345678, 0.12345679) return true
msg则是断言失败后的提示内容。
3.5.2 assertRegex
assertRegex是用正则表达式的方式来进行对象结果断言的方法,也正是因为有正则表达式的存在,让很多的内容格式场景有可以用上这个断言,比如我们最熟悉的邮箱格式验证、手机格式验证等。
邮箱格式验证
class TestString(unittest.TestCase):
def test_email_validation(self):
email = 'test_center@test.com'
regex = r'^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$'
self.assertRegex(email, regex, msg='邮箱格式错误。')
手机格式验证
import re
import unittest
class TestMobile(unittest.TestCase):
def test_valid_mobile(self):
pattern = re.compile(r'^1[3-9]\d{9}$')
valid_mobiles = ['13112345678', '13912345678', '14712345678']
for mobile in valid_mobiles:
with self.subTest(mobile=mobile):
self.assertTrue(pattern.match(mobile), msg=f"{mobile} 是无效手机号")
当然除了这两个之外还一些其他的断言方式,比如assertJsonEqual、assertLogs、assertCountEqual等,但这些都不太适合在UI自动化测试中进行断言使用,所以就不做详细的介绍了。大家在日常的设计编写工作中如果可以按照上述的这些原则与技巧进行执行,相信设计出来的测试用例一定会有效的提升后续对应测试的工作效率。