不管是市场需求还是测试效率而言,自动化测试都是作为测试工程师需要掌握的一门技术,并且在公司能够逐步的应用到常规的测试中,如回归测试。自动化测试的价值在于它能够有效的检测被测对象的质量并且能够给出有价值的结果信息,而且这个结果需要具备权威性,不需要太多人为的参与与干预。
自动化测试最担心的是自动化测试执行结果是通过的,但是被测的对象存在质量上的问题,这就导致自动化测试的价值以及信任度在这一瞬间就会全面崩塌。
当雪崩的时候,没有一片雪花是漂亮的。
自动化测试崩塌也是如此。
那么通过什么样的方式方法以及策略,能够达到自动化测试执行结果是有效并且具备价值的。任何事物都是通过点作为切入度,最后形成一个面,自动化测试最小颗粒度是测试用例,那么我们可以通过这个点来进行切入。在编写的自动化测试用例中需要注意的事项以及测试用例的规范,下面详细阐述这部分。
准确性
每个自动化测试用例都必须得有断言并且每个测试用例只验证一个测试场景,没有测试断言的自动化测试用例是没有价值的,也不是一个有效的测试用例。不管是从功能测试角度还是自动化测试角度,这句话都是适用的。对于不同形式的自动化,测试断言的策略是一样的,只不过验证的维度是不一样的。比如针对一个两个数相加的函数,案例代码如下。
代码语言:javascript复制from flask import jsonify,request
from flask_restful import Api,Resource
from flask import Flask
app=Flask(__name__)
api=Api(app=app)
def add(a,b):
return a b
class Add(Resource):
def post(self):
if not request.json:
return jsonify({'status':1001,'msg':'请求参数不是JSON的数据,请检查,谢谢!'})
else:
data = {
'a': request.json.get('a'),
'b': request.json.get('b'),
'done': True
}
data=add(data.get('a'),data.get('b'))
print(data)
return {'status':0,'msg': '计算成功','result':data}
api.add_resource(Add,'/v1/add')
if __name__ == '__main__':
app.run(debug=True,port='5000',host='0.0.0.0')
如上假设为被测试的对象代码,那么针对不同层次的测试策略它的断言策略是一致的但是断言方式是不一样的,如单元测试更多验证的是add()函数在两个加相加后返回的实际结果是否与期望结果的一致,但是API的测试结果验证不仅仅是结果值的验证。针对API的测试结果验证,需要在三个维度来进行验证,具体是:
- 协议状态码
- 业务状态码
- 返回的响应数据结果的验证
针对如上单元测试验证与API测试验证,编写的测试代码如下。
代码语言:javascript复制#! /usr/bin/env python
# -*- coding:utf-8 -*-
import pytest
from index import add
import requests
def test_unit_test():
assert add(2,3)==5
def test_api_test():
r=requests.post(
url='http://localhost:5000/v1/add',
json={'a':3,'b':9},
headers={'Content-Type':'Application/json'})
assert r.status_code==200
assert r.json()['status']==0
assert r.json()['result']==12
if __name__ == '__main__':
pytest.main(["-s","-v","test_index.py"])
如上仅仅是针对单元测试与API测试的维度,如果是端到端的UI自动化测试,它的断言就是获取计算结果后的文本信息值与期望结果值的对比。所以,每个测试维度的断言一定要合理,这样程序在出问题的情况下也是能够验证出它的问题。
独立性
业务之间是有关联关系的,但是编写的自动化测试用例都必须是独立的,测试用例与测试用例之间不要相互依赖,一旦设计成相互依赖,导致的结果是一个测试用例执行失败,导致后续所有的测试用例执行失败。保持测试用例的独立性遵守如下几个约束。
- 每个测试用例都测试场景的闭环
- 每个测试用例都需要考虑初始化操作与清理操作
- 千万不要刻意的设计具体的测试用例去依赖其他测试用例的业务步骤
根据如上的约束,结合一个具体的案例来说明这部分,假设一个用户管理的系统,具备增删改查的业务功能,并且用户昵称是唯一且不可重复的。编写的测试用例是查询用户信息,初始化与清理操作很好理解,就是添加用户与删除用户,关于第一点完成场景的闭环指的是不管是查询用户还是修改用户的信息,测试用例执行完成后都需要删除用户,这样的目的是不管是QA环境还是线上环境,测试用例执行结束后,都做到没有对系统造成任何的垃圾数据,同时也保持系统的干净性。
动态性
特别是在API的测试中由于业务关联性的特点,导致业务流转过程中会有很多的动态数据,这些动态数据在每次业务操作的过程中都是动态变化的,很难使用静态的数据来完成业务的闭环测试。如最常见的是登录认证授权以及业务流转过程中涉及增加数据这部分,它的ID都是数据都是动态性的。关于这点可以使用API测试技术中的两种思路来解决参数的动态关联,具体如下。
- 函数返回值
- Fixture函数
通过如上思路就可以很轻松的解决了动态参数的关联,如下代码是针对登录成功后返回的TOKEN的处理,代码如下。
代码语言:javascript复制#! /usr/bin/env python
# -*- coding:utf-8 -*-
import pytest
import allure
import requests
@allure.title("获取登录成功后的token")
@pytest.fixture()
def getToken():
r=requests.post(
url='http://47.95.142.233:8000/login/auth/',
json={"username":"13484545195","password":"asd888"},
headers={'Content-Type':'Application/json'})
return r.json()['token']
@pytest.fixture()
def headers(getToken):
return {'Authorization':'JWT {token}'.format(token=getToken)}
@allure.title("验证系统首页")
def test_shouYe(headers):
r=requests.get(
url='http://47.95.142.233:8000/interface/index',
headers=headers)
assert r.status_code==200
assert r.json()['count']['api']==0
if __name__ == '__main__':
pytest.main(["-s","-v","test_index.py"])
如上代码就是通过Fixture函数来处理动态参数值传递的过程。
完整性
端到端的自动化测试解决方案还是API自动化测试,都需要站在业务驱动模式的基础上来达到测试业务的完整性,这样可以达到两个目的:业务质量保障与测试效率提升。所以针对业务完整性以及业务的覆盖率是非常重要的。这就涉及一个度量,其实针对度量这部分在每个组织都是有各自的考量,核心的是只要这个组织认可这个衡量的标准就可以了。比如经常会被问到的一个话题是自动化测试业务覆盖率是多少?这个就不是自动化测试用例个数多少来衡量,自动化测试用例个数多不代表覆盖率多,那么衡量的标准是什么呢?比如覆盖率=已经实现场景数/总的场景数,这也是度量的一种策略与方式,只要团队认可就可以按照这种策略去执行。
模块化
不管多简单的业务流程,一个完整的业务流程走下来,它的测试代码都是非常多的,这产生的问题就是一个测试用例里面有很多的代码,坦白说这对后期的维护成本是很高的,解决的思路是什么了。可以根据封装的思想把共同的操作行为封装成一个函数,比如针对登录的操作,它的步骤是输入账户、密码、点击登录的按钮,那么可以封装成一个登录的函数,案例代码如下:
代码语言:javascript复制#! /usr/bin/env python
# -*- coding:utf-8 -*-
from playwright.sync_api import Page
import pytest
import allure
def getErrorText(page: Page):
''''获取登录失败错误信息'''
return page.locator(
'body > div.mailLoginBox > div > div.mainBox.bg1 > div > div > div:nth-child(6) > div.loginBox > div.freeMailbox > div.freeError > span.loginError.tip11').text_content()
def login(page:Page,username='wuya1303@sina.com', password='admin123'):
with allure.step("输入登录账户"):
page.locator('#freename').fill(username)
with allure.step("输入登录账户密码"):
page.locator('#freepassword').fill(password)
with allure.step("点击登录按钮"):
page.locator(
'body > div.mailLoginBox > div > div.mainBox.bg1 > div > div > div:nth-child(6) > div.loginBox > div.freeMailbox > div.loginOrRegister.clearfix > div.loginRegisterSubmit > a.loginBtn').click()
def sendEemail(page:Page,to='2839168630@qq.com',subject='AutomationTest'):
with allure.step("点击写信按钮"):
page.locator('#coreBtn > ul > li.wrWriteBtn').click()
with allure.step("输入收件人信息"):
page.locator('#tr_to > td > ul > li > input[type=text]').fill(to)
with allure.step("输入主题"):
page.locator('#panel_left > form > div > table > tbody > tr.fwSubject > td > input').fill(subject)
with allure.step("点击发送按钮"):
page.locator('#panel_main > div.wui-Toolbar.altBrdB > span > span:nth-child(2) > a > i.mailPubText').click()
@allure.title("新浪邮箱:验证登录信息为空的错误提示信息")
def test_sina_username_div(page:Page):
page.goto("https://mail.sina.com.cn/")
login(page=page,username='',password='')
with allure.step('验证结果信息'):
assert getErrorText(page=page)=='请输入邮箱名'
@allure.title("新浪邮箱:发送邮件")
def test_sina_send_email(page:Page):
page.goto("https://mail.sina.com.cn/")
login(page=page, username='wuya1303@sina.com', password='admin123')
sendEemail(page=page)
with allure.step("验证邮件已发送"):
successText=page.locator('#send_normal > div > p.title').text_content()
assert successText=='您的邮件已发送'
if __name__ == '__main__':
pytest.main(["-s","-v","test_login.py","--browser chromium"])
如上针对登录与发送邮件单独的封装成一个函数,这样在测试用例中直接调用具体的函数就可以了。