エンタープライズ レベルの Pytest 自動テスト フレームワークのスクリプト概要については、この記事を読むだけで十分です。


序文

使用したナレッジポイント:
1. リクエスト
2. pytest
3. Excel テーブル操作
4. メール送信
5. ログ機能
6. プロジェクト開発仕様書のカタログ適用
7. 魅力
①タイトルナレッジポイント
②説明ナレッジポイント

エンタープライズ プロジェクト フレームワークの要件

1. Excel からレコード行を読み取ります (各行は API (URL、リクエスト タイプ、名前、説明、パラメータ、期待値)) 2. 各リクエストにパラメータ化を使用し、リクエストを送信および取得するためにリクエストを使用します。結果、レコードからフィールドを抽出します
。結果を取得し、期待値に対してアサーションを行います。
3. allure を使ってテストレポートを作成する
① 各リクエストケースにタイトルと説明を追加します

4. テスト レポートを電子メールで送信し、
魅力レポート フォルダーを zip に圧縮して、zip ファイルを送信します。

5.ログログにキーポイントを追加
①リクエスト時
②アサート時
③オプションパッケージ時
④Excel読み込み時

6. 結合を理解するには、ソフトウェア開発仕様に従う必要があります。
① データ フォルダー
② 設定フォルダー
③ スクリプト フォルダー

自動フレームワーク分析

まず pytest スクリプトの実行を検討し、これに基づいて関数を追加します

1. ログを追加します。
2. Excel テーブルの操作。期待値の json 文字列を辞書にロードする必要があることに注意してください。
3. リクエスト操作。Excel テーブルから各行のデータを取得し、リクエストを送信します。
① の期待値リクエストが複数の場合もあり、ループ判定を行います

②リクエストのレスポンス結果は様々な種類が考えられますが、response.headers[“Content-Type”]からどのようなレスポンスであるかを判断し反映します。

4. ターミナルコマンドを実行します:
①subprocess
②os.popen

5. ファイルの圧縮、電子メールの送信、レポートフォルダーの削除

自動化フレームワークの完成

1.
コメント部分に中国語が含まれている場合でも、構成 ini ファイル pytest.ini ファイルに中国語を含めないでください。

[pytest]

addopts = -s -v -p no:warnings --alluredir ./report/allure_json
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*

2. ディレクトリ構造を作成する

B2

3. エントリーファイルの作成

import pytest

if __name__ == '__main__':
  pytest.main()

4. 設定ファイルの設定(ログ関連、Excel関連、Allure関連、メール関連)

import os
import sys
import datetime
# 根目录:
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# ---------------- 日志相关 --------------------
# 日志级别:
LOG_LEVEL = 'debug'
LOG_STREAM_LEVEL = 'debug'  # 屏幕输出流
LOG_FILE_LEVEL = 'info'   # 文件输出流
# 日志文件夹命名:
LOG_FILE_NAME = os.path.join(BASE_PATH, 'logs', datetime.datetime.now().strftime('%Y-%m-%d') + '.log')
# ---------------- Excel相关 --------------------
# Excel数据文件夹命名:
DATA_PATH = os.path.join(BASE_PATH, 'data')
# 切换data文件时,只需要更改文件名即可:
EXCEL_FILE_NAME = 'cnodejs接口.xlsx'
EXCEL_FILE_PATH = os.path.join(DATA_PATH, EXCEL_FILE_NAME)
if __name__ == '__main__':
    # 添加顶级目录:
    sys.path.insert(0, BASE_PATH)
    print(sys.path)
# ---------------- Allure相关 ----------------------
# 报告路径:
REPORT_PATH = os.path.join(BASE_PATH, 'report')
# Allure的json文件夹命名:
ALLURE_JSON_DIR_NAME = 'allure_json'
# Allure的json文件路径:
ALLURE_JSON_DIR_PATH = os.path.join(REPORT_PATH, ALLURE_JSON_DIR_NAME)
# allure的html文件夹命名:
ALLURE_REPORT_DIR_NAME = os.path.join(REPORT_PATH, 'allure_report')
# allure的html报告路径
ALLURE_REPORT_DIR_PATH = os.path.join(REPORT_PATH, ALLURE_REPORT_DIR_NAME)
# 压缩包路径:
ZIP_FILE_PATH = os.path.join(REPORT_PATH, 'report.zip')
# 生成报告命令:
ALLURE_COMMAND = "allure generate {} -o {}".format(ALLURE_JSON_DIR_PATH, ALLURE_REPORT_DIR_PATH)
# ------------------- 邮件相关 -----------------
# 第三方 SMTP 服务:
MAIL_HOST = "smtp.qq.com"
MAIL_USERNAME = "[email protected]"
MAIL_TOKEN = "mpaocydzpzfjidge"
# 收件人,收件人可以有多个,以列表形式存放:
RECEIVERS = ['[email protected]', '[email protected]']
# 邮件主题、收件人、发件人:
MAIL_SUBJECT = 'Python通过第三方发邮件'
# 可修改:设置收件人和发件人
SENDER = '[email protected]'  # 发件人
# 邮件正文
MAIN_CONTENT = 'hi man:\r这是今日的{excel_file_name}的测试报告\r详情下载附件\r查看方法,终端执行 allure open report\r测试人:{execute_tests_name},联系电话:{execute_tests_phone} \r{execute_tests_date}'.format(
    excel_file_name=EXCEL_FILE_NAME,
    execute_tests_name="张达",
    execute_tests_phone=1733813xxxx,
    execute_tests_date=datetime.datetime.date(datetime.datetime.now())
)

5. テストケース test_case (ログ出力、動的参加、メール送信)

import pytest
import allure
# 引入配置文件:
from conf import settings
# 引入日志功能:
from util.LogHandler import logger
# 引用Excel表操作功能:
from util.ExcelHandler import ExcelHandler
# 引入发请求功能:
from util.RequestHhandler import RequestHandler
# 引入报告功能:
from util.AllureHandler import AllureHandler
# 引入发邮件功能:
from util.SendMailHandler import SendMailHandler
class TestCase(object):
    @pytest.mark.parametrize("item", ExcelHandler().get_excel_data(settings.EXCEL_FILE_PATH))
    def test_case(self, item):
        # 调用日志功能的info级别:
        # logger().info(item)
        # 获取每一行数并且发请求:
        response = RequestHandler().get_response(item)
        # 行为驱动标记:
        allure.dynamic.feature(item['case_title'])
        allure.dynamic.story(item['case_description'])
        # 动态加参:
        allure.dynamic.title(item['case_title'])
        allure.dynamic.description(
            "<b style='color:red;'>描述:</b>{}<br />"
            "<b style='color:red;'>请求的url:</b>{}<br />"
            "<b style='color:red;'>预期值:</b>{}<br />"
            "<b style='color:red;'>实际执行结果:</b>{}<br />".format(
                item['case_description'],
                item['case_url'],
                response[0],
                response[1]
            ))
        assert response[0] == response[1]
    def teardown_class(self):
        # 参数化用例都执行完毕才执行的操作:
        logger().info('teardown_class')
        # 执行allure命令,生成allure报告
        AllureHandler().execute_command()
        # 将测试报告打包并发送邮件:
        SendMailHandler().send_mail_msg()

6. アリュール機能(ターミナルコマンド実行)

# 执行终端命令、subprocess要代替一些老旧的模块命令:
from subprocess import Popen, call
from conf import settings
from util.LogHandler import logger
class AllureHandler(object):
    # 读取json文件,生成allure报告:
    def execute_command(self):
        # Python执行终端命令:shell=True:将['allure', 'generate', '-o', 'xxxx']替换为'allure generate -o'
        try:
            call(settings.ALLURE_COMMAND, shell=True)
            logger().info('执行allure命令成功')
        except Exception as e:
            logger().error("执行allure命令失败,详情参考: {}".format(e))

7. ログ機能(ログレベルの設定、出力ログのフォーマット)

import logging
from conf import settings
class LoggerHandler:
    # 日志操作
    _logger_level = {
    
    
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
        'critical': logging.CRITICAL
    }
    def __init__(self, log_name, file_name, logger_level, stream_level='info', file_level='warning'):
        self.log_name = log_name
        self.file_name = file_name
        self.logger_level = self._logger_level.get(logger_level, 'debug')
        self.stream_level = self._logger_level.get(stream_level, 'info')
        self.file_level = self._logger_level.get(file_level, 'warning')
        # 创建日志对象
        self.logger = logging.getLogger(self.log_name)
        # 设置日志级别
        self.logger.setLevel(self.logger_level)
        if not self.logger.handlers:
            # 设置日志输出流
            f_stream = logging.StreamHandler()
            f_file = logging.FileHandler(self.file_name)
            # 设置输出流级别
            f_stream.setLevel(self.stream_level)
            f_file.setLevel(self.file_level)
            # 设置日志输出格式
            formatter = logging.Formatter(
                "%(asctime)s %(name)s %(levelname)s %(message)s"
            )
            f_stream.setFormatter(formatter)
            f_file.setFormatter(formatter)
            self.logger.addHandler(f_stream)
            self.logger.addHandler(f_file)
    @property
    def get_logger(self):
        return self.logger
def logger(log_name='接口测试'):
    return LoggerHandler(
        log_name=log_name,
        logger_level=settings.LOG_LEVEL,
        file_name=settings.LOG_FILE_NAME,
        stream_level=settings.LOG_STREAM_LEVEL,
        file_level=settings.LOG_FILE_LEVEL
    ).get_logger
if __name__ == '__main__':
    logger().debug('aaaa')
    logger().info('aaaa')
    logger().warning('aaaa')

8. Excel関数(ループ追加・リスト解析用)

import xlrd
from conf import settings
class ExcelHandler(object):
    # Excel功能:
    def __init__(self, excel_file_path=None):
        self.excel_file_path = excel_file_path
    def get_excel_data(self, excel_file_path):
        # 读取Excel表格:
        book = xlrd.open_workbook(excel_file_path)
        # 根据sheet名称获取sheet对象:
        sheet = book.sheet_by_name('自动化测试')
        # 获取标题:
        title = sheet.row_values(0)
        # l = []
        # 方式一循环添加返回列表:
        # for row in range(1, sheet.nrows):
        #     l.append(dict(zip(title, sheet.row_values(row))))
        # return l
        # 方式二列表解析式返回:
        return [dict(zip(title, sheet.row_values(row))) for row in range(1, sheet.nrows)]
    def write_excel(self):
        # 写入Excel表格:
        pass
if __name__ == '__main__':
    ExcelHandler().get_excel_data(settings.EXCEL_FILE_PATH)

9. リクエスト関数(BS解析、プライベートメソッドの定義、JSON戻り型の別処理)

import json
import requests
# 导入解析功能:
from bs4 import BeautifulSoup
# 引入日志功能:
from util.LogHandler import logger
class RequestHandler(object):
    # 发请求功能:
    def get_response(self, item):
        # 获得请求结果:
        # logger().info(item)
        return self._send_msg(item)
    def _send_msg(self, item):
        # 发请求的操作(私有的):
        response = requests.request(
            # 请求类型:
            method=item['case_method'],
            # 请求url:
            url=item['case_url'],
            # 处理请求中携带的data数据(私有的)
            data=self._check_data_msg(item),
            # 处理请求中的请求头(私有的)
            headers=self._check_headers_msg(item)
        )
        # 分不同的json返回类型处理:
        Content_Type = response.headers['Content-Type'].split('/')[0]
        # 反射:
        if hasattr(self, '_check_{}_response'.format(Content_Type)):
            obj = getattr(self, '_check_{}_response'.format(Content_Type))
            res = obj(response, item)
        else:
            pass
        return res
    def _check_application_response(self, response, item):
        # 处理json类型的响应:
        response = response.json()
        expect = json.loads(item['case_expect'])
        for key, value in expect.items():
            # 意味着预期值的字段跟实际请求结果的字段不一致:断言失败
            if value != response.get(key, None):
                logger().info('请求:{} 断言失败,预期值是:[{}] 实际执行结果:[{}], 相关参数:{}'.format(
                    item['case_url'],
                    value,
                    response.get(key, None),
                    item
                ))
                return (value, response.get(key, None))
        else:
            # 断言成功
            return (value, response.get(key, None))
    def _check_text_response(self, response, item):
        # 处理文本类型的响应:
        response.encoding = 'utf-8'
        soup = BeautifulSoup(response.text, 'html.parser')
        title = soup.find('title').text
        return (title, item['case_expect'])
    def _check_image_response(self, response, item):
        # 处理jpeg图片类型的响应:
        pass
    def _check_data_msg(self, item):
        # 处理请求中携带的data数据(私有的):
        if item.get('case_data', None):
            # 如果请求中有data参数:
            pass
        else:
            return {
    
    }
    def _check_headers_msg(self, item):
        # 处理请求中的请求头(私有的)、预留接口负责处理,请求头相关的逻辑
        # 自定义一些固定的请求头,也可以将Excel表格中的特殊请求头更新到这个字典中
        headers = {
    
    }
        if item.get('case_headers', None):
            headers.update(json.loads(item['case_headers']))
        return headers
if __name__ == '__main__':
    pass

10.メール機能(圧縮ファイル、メール設定、メール、一時ファイルの自動削除)

import os
# 导入删除文件夹/文件模块:
import shutil
# 导入压缩模块:
import zipfile
# 导入日期模块:
import datetime
# 导入发邮件模块:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
# 导入配置文件:
from conf import settings
from util.LogHandler import logger
class SendMailHandler(object):
    # 将测试报告压缩并发送邮件
    def _check_zip_file(self):
        # 将测试报告压缩:
        base_dir = settings.ALLURE_REPORT_DIR_PATH
        # 压缩包路径:
        zip_file_path = settings.ZIP_FILE_PATH
        f = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED)
        for dir_path, dir_name, file_names in os.walk(base_dir):
            # 要是不replace,就从根目录开始复制:
            file_path = dir_path.replace(base_dir, '')
            # 实现当前文件夹以及包含的所有文件:
            file_path = file_path and file_path + os.sep or ''
            for file_name in file_names:
                f.write(os.path.join(dir_path, file_name), file_path + file_name)
        f.close()
    def send_mail_msg(self):
        # 调用压缩功能:
        self._check_zip_file()
        # 调用发邮件操作:
        self._send_mail()
        # 调用删除临时文件:
        self._del_temp_file()
    def _send_mail(self):
        # 发邮件操作:
        # 第三方 SMTP 服务:
        mail_host = settings.MAIL_HOST
        # 下面两个,可修改:
        mail_user = settings.MAIL_USERNAME
        mail_pass = settings.MAIL_TOKEN
        # 可修改:设置收件人和发件人:
        sender = settings.SENDER
        # 收件人,收件人可以有多个,以列表形式存放:
        receivers = settings.RECEIVERS
        # 创建一个带附件的实例对象:
        message = MIMEMultipart()
        # 可修改邮件主题、收件人、发件人:
        subject = settings.MAIL_SUBJECT
        # 下面三行不用修改:
        message['Subject'] = Header(subject, 'utf-8')
        # 发件人:
        message['From'] = Header("{}".format(sender), 'utf-8')
        # 收件人:
        message['To'] = Header("{}".format(';'.join(receivers)), 'utf-8')
        # 邮件正文内容 html 形式邮件:相当于是一个报告预览:
        # 可修改:send_content
        content = settings.MAIN_CONTENT
        # 第一个参数为邮件正文内容:
        html = MIMEText(_text=content, _subtype='plain', _charset='utf-8')
        # 构造附件:
        send_content = open(settings.ZIP_FILE_PATH, 'rb').read()
        # 只允许修改第一个参数,后面两个保持默认:
        att = MIMEText(_text=send_content, _subtype='base64', _charset='utf-8')
        # 不要改:
        att["Content-Type"] = 'application/octet-stream'
        # 页面中,附件位置展示的附件名称:
        file_name = 'report.zip'
        # 下面3行不要改、filename 为邮件附件中显示什么名字:
        att["Content-Disposition"] = 'attachment; filename="{}"'.format(file_name)
        message.attach(html)
        message.attach(att)
        try:
            smtp_obj = smtplib.SMTP()
            smtp_obj.connect(mail_host, 25)
            smtp_obj.login(mail_user, mail_pass)
            smtp_obj.sendmail(sender, receivers, message.as_string())
            smtp_obj.quit()
            logger().info("邮件发送成功")
        except smtplib.SMTPException as e:
            logger().error("Error: 邮件发送失败,详情:{}".format(e))
    def _del_temp_file(self):
        # 清空report目录:
        logger().info('删除临时目录')
        shutil.rmtree(settings.REPORT_PATH)
        logger().info('删除临时目录成功')
if __name__ == '__main__':
    # 在当前py文件测试:
    SendMailHandler().send_mail_msg()
以下は、私がまとめた 2023 年の最も完全なソフトウェア テスト エンジニア学習知識アーキテクチャ システム図です。

1. Pythonプログラミングの入門から習得まで

画像の説明を追加してください

2.インターフェース自動化プロジェクトの実戦

画像の説明を追加してください

3. Web自動化プロジェクトの実戦

画像の説明を追加してください

4. アプリ自動化プロジェクトの実戦

画像の説明を追加してください

5. 一流メーカーの再開

画像の説明を追加してください

6. DevOps システムのテストと開発

画像の説明を追加してください

7. 一般的に使用される自動テストツール

画像の説明を追加してください

8、JMeterのパフォーマンステスト

画像の説明を追加してください

9. まとめ(最後にちょっとしたサプライズ)

闘争は燃える炎であり、粘り強さが成功への道です。たとえ道がどんなに曲がりくねっていても、勇気と情熱を持って夢のために戦い続けなければなりません。自分の力を信じ、限界を超え、輝かしい旅に出よう!

粘り強く追求し、懸命に戦い、未知のものに勇敢に挑戦してください。困難は踏み台、挫折は試金石、あらゆる粘り強さは成功への一歩です。情熱は魂を燃やし、信仰は私たちを前進させ、汗を流して私たち自身の素晴らしい章を書きます。

困難を恐れず、卓越性を追求し、失敗を恐れずに最善を尽くします。闘争の道では、献身と収穫が一緒に踊り、成功には粘り強さが伴います。自分の能力を信じて、勇敢に前進し、輝く人生を自分で切り開いていきましょう!

おすすめ

転載: blog.csdn.net/m0_70102063/article/details/132083390