从零开始搭建自动化测试工具:详细指南(二)

在前一篇文章中,我们介绍了从零开始搭建自动化测试工具的技术选型。接下来,本文将深入探讨如何使用Pytest-BDD框架,结合Selenium和Requests库,实现UI和接口自动化测试的统一,并封装这些工具以适应Gherkin语言编写的测试用例。

selenium封装

初始化实现

为了启动浏览器并进入测试页面,我们首先需要实现初始化步骤。

Gherkin场景:

Feature: UI自动化测试

    Scenario: 登录测试
        Given 初始化

python实现:

class SeleniumTool():
    def __init__(self):
        self.options = webdriver.ChromeOptions()
        self.options.add_argument("--start-maximized")
        self.driver = webdriver.Chrome(options=self.options)
        self.driver.get(url)


@pytest.fixture
@given('初始化')
def selenium_tool():
    return SeleniumTool()

注意: 我们要测试的URL,可以通过配置文件方式实现,本篇内容先不讲,后面会统一介绍。

获取页面元素

为了简化元素定位,我们将页面元素封装成类,并创建一个方法来获取这些元素:

from selenium import webdriver
from selenium.webdriver.common.by import By


class LoginPageElements:
    def __init__(self):
        self.by_mapping = {
            'ID': By.ID,
            'class': By.CLASS_NAME,
            'css': By.CSS_SELECTOR,
            'name': By.NAME,
            'link text': By.LINK_TEXT,
            'tag name': By.TAG_NAME,
            'xpath': By.XPATH
        }
        self.login_button = ('xpath', '//*[text()=\'登录\']')
        self.account_input = ('ID', 'account')
        self.passwd_input = ('ID', 'passwd')

    def login_page(self):
        login_page = {
            '登录按钮': self.login_button,
            '用户名输入框': self.account_input,
            '密码输入框': self.passwd_input
        }
        return login_page

    def get_element(self, driver: webdriver, element_name):
        element_data = self.login_page()[element_name]
        by_type, value = element_data
        by_method = self.by_mapping[by_type]
        element = driver.find_element(by_method, value)
        return element

点击操作

Gherkin场景:

When 点击"登录按钮"

python实现:

@when(parsers.parse('点击 "{button_element}"'))
def click_button(selenium_tool, button_element):
    # 使用对应的page页面元素
    page = LoginPageElements()
    element = page.get_element(selenium_tool.driver, button_element)
    element.click()
输入操作

Gherkin场景:

When 在元素"用户名输入框"中,输入"admin"

python实现:

@when(parsers.parse('在元素 "{user_name_element}" 中,输入 "{text}"'))
def send_username(selenium_tool, user_name_element, text):
    # 使用对应的page页面元素
    page = LoginPageElements()
    input_element = page.get_element(selenium_tool.driver, user_name_element)
    input_element.send_keys(text)

[!NOTE]

  • 在实际应用中,我们可能需要为每个页面都创建一个类似的PageElements类,以组织和管理该页面的所有元素。
  • 我们还可以通过配置文件或环境变量来管理测试URL和其他配置信息,使测试更加灵活和可配置。
  • 为了保持测试的独立性和可重复性,建议在每个测试场景结束后关闭浏览器或重置浏览器状态。

requests封装

针对于requests的封装,我们将接口请求封装成一个公共函数,在python实现Gherkin场景时,我们只需要调用公共的接口请求函数,可以增加工具的易用性和可维护性。

接口用例Gherkin场景:

Feature: 登录接口测试

    Scenario: 登录接口测试
        Given 初始化
        When 调用"/login"接口
        When 使用"post"请求    # post get put delete
        When 参数类型"json"    # params json form-data form-urlencoded
        When 请求头"{'Content-Type': 'application/json'}"
        When 参数"{'username': 'test', 'password': 'password'}"
        When 校验类型"包含校验"    # 校验类型: 包含校验 相等校验 字段类型校验 字段值校验 状态码校验
        When 校验文本"成功"
        When 校验字段类型为"str"    # 字段类型:int str float list dict bool
        When 校验字段"loginInfo"
        Then 接口调用成功

python实现:

from pytest_bdd import scenarios, given, when, then, parsers
from pytest_bdd.hooks import *

from common.api_tool import api_test
from common.token_api import get_cms_token, do_logout
from common.assert_tool import assert_tool
from common.yaml_tool import read_yaml
from common.assert_tool import analysis_dict
import ast
import os
import logging


test_result_type_list = {
    '相等校验': 0,
    '包含校验': 1,
    '字段类型校验': 2,
    '字段值校验': 3,
    '状态码校验': 4
}


class ApiTest:
    def __init__(self):
        self.api = None
        self.methods = 'get'
        self.data_type = 'params'
        self.params = {}
        self.headers = {}
        self.test_result_type = None
        self.result_text = None
        self.result_type = None
        self.result_data_type = None
        self.result_key = None
        self.file = None
        self.is_logout = False

    def call_api(self, test_body):
        response = api_test(test_body)
        return response


@pytest.fixture
@given('初始化')
def api_tool():
    return ApiTest()


@when(parsers.parse('调用 "{url}" 接口'))
def api(api_tool, url):
    api_tool.api = url


@when(parsers.parse('使用 "{methods}" 请求'))
def methods(api_tool, methods):
    api_tool.methods = methods


@when(parsers.parse('参数类型 "{data_type}"'))
def data_type(api_tool, data_type):
    api_tool.data_type = data_type


@when(parsers.parse('请求头 "{headers}"'))
def headers(api_tool, headers):
    headers_dict = ast.literal_eval(headers)
    for key, value in headers_dict.items():
        api_tool.headers[key] = value


@when(parsers.parse('参数 "{params}"'))
def params(api_tool, params):
    api_tool.params = ast.literal_eval(params)


@when(parsers.parse('校验类型 "{test_result_type}"'))
def result_test(api_tool, test_result_type):
    api_tool.test_result_type = test_result_type_list[test_result_type]


@when(parsers.parse('校验文本 "{result_text}"'))
def result_text(api_tool, result_text):
    api_tool.result_text = result_text


@when(parsers.parse('校验字段类型为 "{result_type}"'))
def result_type(api_tool, result_type):
    api_tool.result_type = result_type


@when(parsers.parse('校验字段 "{result_key}"'))
def result_key(api_tool, result_key):
    api_tool.result_key = result_key


@when(parsers.parse('上传文件 "{files}"'))
def file(api_tool, files):
    api_tool.file = files


@then('调用成功')
def asserts(api_tool):
    test_data = {'test_result': {'text': api_tool.result_text, 'type': api_tool.test_result_type, 'result_type': api_tool.result_type, 'key': api_tool.result_key}}
    test_body = {'URL': api_tool.api, 'method': api_tool.methods, 'data_type': api_tool.data_type, 'headers': api_tool.headers, 'params': api_tool.params, 'file': api_tool.file}
    response = api_tool.call_api(test_body)
    logging.info('接口返回:' + str(response.text))
    if api_tool.is_logout:
        do_logout(api_tool.headers['ticket'])
    if api_tool.test_result_type is not None:
        assert_tool(response, test_data)

上面我们主要把每个步骤的参数拼装成一个完成的接口请求。然后在场景then '调用成功' 去实现调用接口并进行断言。来验证接口测试是否成功。

api_tool 封装
import requests
import os
import json
from common.yaml_tool import read_yaml
from common.token_api import get_token

def api_test(test_body):
    config = read_yaml('config/config.yaml')
    config_env = config['env']
    data_type = test_body['data_type'].lower()
    url = config_env + test_body['URL']
    method = test_body['method'].lower()
    data = test_body['params']
    headers = test_body.get('headers', {})
    file = test_body.get('file', None)

    if data_type not in ['params', 'json', 'form-data', 'form-urlencoded']:
        return {'code': '5000', 'msg': '测试脚本暂时不支持的参数类型'}

    def make_request():
        if data_type == 'params':
            return params(url, method, data, headers)
        elif data_type == 'json':
            return json(url, method, data, headers)
        elif data_type == 'form-data':
            return formdata(url, method, data, headers, file)
        elif data_type == 'form-urlencoded':
            return urlencoded(url, method, data, headers)

    response = make_request()
    return response


def params(url, method, data, headers):
    methods = {
        'post': requests.post,
        'put': requests.put,
        'get': requests.get,
        'delete': requests.delete
    }
    try:
        response = methods[method](url, params=data, headers=headers)
        response.encoding = 'utf-8'
        return response
    except Exception as e:
        return {"code": "5000", "msg": str(e)}
    

def json(url, method, data, headers):
    methods = {
        'post': requests.post,
        'put': requests.put,
        'get': requests.get,
        'delete': requests.delete
    }
    try:
        response = methods[method](url, json=data, headers=headers)
        return response
    except Exception as e:
        return json.dumps({'code': '5000', 'msg': '测试脚本报错:' + str(e)})
    

def urlencoded(url, method, data, headers):
    methods = {
        'post': requests.post,
        'put': requests.put,
        'get': requests.get,
        'delete': requests.delete
    }
    try:
        response = methods[method](url, data=data, headers=headers)
        return response
    except Exception as e:
        return {'code': '5000', 'msg': '测试脚本报错:' + str(e)}
    

def formdata(url, method, data, headers, file):
    if file:
        file_name = file
        name = os.path.splitext(file_name)[-1]
        file_types = {
            '.doc': 'application/msword',
            '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            '.pdf': 'application/pdf',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.png': 'image/png',
            '.ppt': 'application/vnd.ms-powerpoint',
            '.xls': 'application/vnd.ms-excel',
            '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            '.txt': 'text/plain'
        }
        file_type = file_types.get(name, None)
        if file_type is None:
            return {'code': '5000', 'msg': '不支持的文件类型'}
        path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
        file = [
            ('file', ('Test'+name, open(path + '/statics/' + file_name, 'rb'), file_type))
        ]

    methods = {
        'post': requests.post,
        'put': requests.put,
        'get': requests.get,
        'delete': requests.delete
    }
    try:
        response = methods[method](url, data=data, files=file, headers=headers)
        return response
    except Exception as e:
        return {'code': '5000', 'msg': '测试脚本报错:' + str(e)}

总结

通过本文,我们介绍了如何使用Pytest-BDD框架,结合Selenium和Requests库,实现UI和接口自动化测试的统一封装。我们创建了Selenium和Requests的封装类,简化了测试用例的编写。接下来,我们将继续介绍日志模块、断言模块等相关内容,进一步完善自动化测试工具。

注意: 在实际项目中,请根据具体需求调整代码结构和逻辑。本文提供的示例仅供参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值