🏆掘金活动多,奖品香;生活像演戏,哭着笑 | 2021年终总结征文大赛

877 阅读14分钟

「时光不负,创作不停,本文正在参加2021年终总结征文大赛

加入掘金

我的掘金账号是:Python研究所,我是2021-7-13加入掘金的,我在掘金关注了14位博主,同时也收获了41为关注者,关关数互为对称数,巧的是今天20211202也算是一个对称数

吉祥给吉祥开门,吉祥到家了。

image.png

我的初衷

实话实说,我加入掘金是被掘金活动的丰富礼物所吸引的(这是不是暴露了我爱贪小便宜的心思)。但是经过5个月的写文经历,我承认我确实越来越喜欢掘金的活动礼品了(O(∩_∩)O哈哈哈~),但同时,我貌似也无意间养成了写文和记录的习惯。我竟然曾花费很多时间去主动寻找好用的编辑器,例如:mdnicevnoe小书匠TyporaNotionDatalore等等(我绝没有打广告,不许误会我)。不得不说,他们各有千秋,但是最终我还是选择了mdnice+vnote,一个在线,一个离线。

始于掘金短袖,终于写文习惯,还是要谢谢掘金,掘金也应谢谢各位博主。(一鞠躬、二鞠躬。。。)

我的成绩

文章

在掘金的半年,我竟然写出了139篇文章(哇哦,为自己点赞),获得了527个赞,平均每篇文章点赞超4个(很满足了)。

在掘金的半年,我主要完成了FastApiPython从常用库(持续更新)Vue简明教程Docker简明教程HttpRunner教程等十多个专栏,部分还在持续更新中。

在掘金的半年,我参加了Python主题月活动,8月更文活动,小知识活动,11月更文活动,收货满满(明年夏天短袖有了,吸尘器有了,双肩包有了,帽子也有了,掘金徽章有了...)

工作

在掘金的半年,我养成了每天上午打卡的习惯。而且我也借鉴打卡为我们的组员定制了每天上午上十点半处理自己待办的定时任务,降低了待办延期率,让我们的工作更加丝滑了。

在掘金的半年,我养成了记录的习惯,同时建立了了内部知识平台&测试平台+很多其他平台。

当然,还有很多,尽情期待。。。

我的生活

今年,是带娃的一年(我有个大胖儿子,可能吃了),是治愈的一年(孩子一笑,还有什么事?),说是幸福的一年(一家三口,大家小家都安稳度过),是辛苦的一年(老婆和妈妈带娃做饭顾家辛苦了),是充实的一年(工作,更文,提升等等),更是难过的一年(婆媳关系,委屈难过,只为全家人都能好好的),最终是珍贵的、值得的、难忘的一年。

时间还长,去做自己想做的事,别留遗憾。。。

彩蛋

就在刚刚,我将昨天写的测试报告生成器TPG(TestReportGenerate)的文章写完了,文至此,意也达,索性就在年终总结的最后,将此文奉上,写的比较初级简单(大佬们,切勿嘲笑,还请指点),主要针对那些想要,或者有计划去做测试报告自动生成的同学。

分割线以下为TPG的文章


背景

很长时间以来,我们都需要针对每个版本出测试报告。尤其是在敏捷后,我们出具测试报告的频率会达到一周甚至更快,为了一定程度上解决这个问题,我打算做一个测试报告生成器。其实是很早也有这个想法,只是一直没能下手做。终于在昨天,我抽空做了一个能够适应我们当前测试现状的一个简易版本测试报告生成器,同时也将它分享出来,希望能够帮到有需要的同学。这个小工具比较简单,还希望各位大佬不要喷我哈。

目的

最初的做次测试报告生成器的目的就是能够规范测试报告,即每个版本的测试报告结构固定;其次就算是能够自动根据测试数据渲染图表,不需要每次都去插入图标,调整格式;最后就是对于部分地方的数据能够进行个性化处理,比如加一些样式什么的;最终目的就算是能够实现一个轻量,简单的测试报告生成器。

概述

最早也计划过使用前后端分离的模式开发一款能够进行数据驻留,多次编辑的测试报告生成器。但是鉴于时间原因,最终我选择了是使用一个超简单的且功能相对强的库Pywebio来直接实现测试报告生成。它不但简单而且还可以方便我的同事对其进行优化。

关于 pywebio

PyWebIO提供了一系列命令式的交互函数来在浏览器上获取用户输入和进行输出,将浏览器变成了一个“富文本终端”,可以用于构建简单的Web应用或基于浏览器的GUI应用。 使用PyWebIO,开发者能像编写终端脚本一样(基于inputprint进行交互)来编写应用,无需具备HTMLJS的相关知识;

PyWebIO还可以方便地整合进现有的Web服务。非常适合快速构建对UI要求不高的应用。

特性

  • 使用同步而不是基于回调的方式获取输入,代码编写逻辑更自然
  • 非声明式布局,布局方式简单高效
  • 代码侵入性小,旧脚本代码仅需修改输入输出逻辑便可改造为 Web 服务
  • 支持整合到现有的 Web 服务,目前支持与Flask、Django、Tornado、aiohttp、 FastAPI(Starlette)框架集成
  • 同时支持基于线程的执行模型和基于协程的执行模型
  • 支持结合第三方库实现数据可视化

关于 TPG

最终,我对这个测试报告生成工具命名为TestReportGenerate,简称TPG,是一个Pywebio APP,仅几百行代码。其以轻量,简单为初心,专注于测试报告生成。当然你也可以根据自己的需要进行修改和完善。

TPG

requirements

python3.7.5

pywebio==1.3.3
pyecharts==1.9.1
pandas==1.3.4
flask==1.1.2
openpyxl==3.0.7

TPG 代码

from pywebio.input import *
from pywebio.output import *
from pywebio.pin import *
from pyecharts import options as opts
from pyecharts.charts import Bar
from pyecharts.globals import ThemeType
from pywebio import start_server
from pywebio.platform import path_deploy,run_event_loop
import pandas as pd

import logging
handler = logging.FileHandler("tpg.log")
formatter= logging.Formatter("%(asctime)s - %(message)s")
handler.setFormatter(formatter)

def ReportGenerate():

    basic_info = input_group('基础信息',[
        input(
            '请输入版本号',
            name='version1',
            type=TEXT,
            placeholder='版本号,例:v3.0.1',  # 占位
            #value='v3.0.1',
            #help_text='This is help text',  # 提示
            required=True,                  # 必填
            datalist=['v3.0.1', 'v3.0.2', 'v3.0.3']    # 常驻输入联想
        ),
        input(
            '请输入迭代',
            name='version2',
            type=TEXT,
            placeholder='迭代,例:1月迭代',  # 占位
            #value='1月迭代',
            #help_text='This is help text',  # 提示
            required=True,                  # 必填
            datalist=['1月迭代', '2月迭代', '3月迭代', '4月迭代', '5月迭代', '6月迭代', '7月迭代', '8月迭代', '9月迭代', '10月迭代', '11月迭代', '12月迭代']    # 常驻输入联想
        ),
        input(
            '请输入起始测试时间',
            name='test_time1',
            value='2022-2-0',
            type=DATE,
            required=True,                  # 必填
        ),
        input(
            '请输入结束测试时间',
            name='test_time2',
            value='2022-2-2',
            type=DATE,
            required=True,                  # 必填
        ),
        input(
            '请输入测试用例链接',
            name='test_case_url',
            type=URL,
            required=True,                  # 必填
            help_text='Jira上测试用例文档的链接',
        ),
    ])

    issue_info = input_group('缺陷信息',[
        input(
            label='缺陷列表URL',
            name='issue_url',
            type=URL,
            required=True,
        ),
        input(
            label='缺陷总数',
            name='issue_total',
            type=NUMBER,
            required=True,                  # 必填
            value=40
        ),
        input(
            label='致命的缺陷数',
            name='die',
            type=NUMBER,
            required=True,                  # 必填
            value=1
        ),
        input(
            label='严重的缺陷数',
            name='critical',
            type=NUMBER,
            required=True,                  # 必填
            value=4
        ),
        input(
            label='一般的缺陷数',
            name='normal',
            type=NUMBER,
            required=True,                  # 必填
            value=8
        ),
        input(
            label='建议的缺陷数',
            name='low',
            type=NUMBER,
            required=True,                  # 必填
            value=10
        ),
        input(
            label='关闭的缺陷数',
            name='closed',
            type=NUMBER,
            required=True,
            value=10
        ),
        input(
            label='拒绝的缺陷数',
            name='refused',
            type=NUMBER,
            required=True,
            value=1
        ),
        # input(
        #     label='修复的缺陷数',
        #     name='fixed',
        #     type=NUMBER,
        #     required=True,
        #     value=1
        # ),
        input(
            label='未解决的缺陷数',
            name='error',
            type=NUMBER,
            required=True,
            value=3
        ),
    ])

    issue_analyse = input_group('缺陷分析',[
        input(
            label='模块1',
            name='portal',
            type=NUMBER,
            required=True,
            value=121
        ),
        input(
            label='模块2',
            name='cmp',
            type=NUMBER,
            required=True,
            value=22
        ),
        input(
            label='模块3',
            name='nova',
            type=NUMBER,
            required=True,
            value=12
        ),
        input(
            label='模块4',
            name='net',
            type=NUMBER,
            required=True,
            value=7
        ),
        input(
            label='模块5',
            name='storage',
            type=NUMBER,
            required=True,
            value=4
        ),
        input(
            label='模块6',
            name='common',
            type=NUMBER,
            required=True,
            value=9
        ),
        input(
            label='模块7',
            name='product',
            type=NUMBER,
            required=True,
            value=15
        ),
        input(
            label='模块8',
            name='RDS',
            type=NUMBER,
            required=True,
            value=18
        ),
        input(
            label='模块9',
            name='arch',
            type=NUMBER,
            required=True,
            value=0
        ),
        input(
            label='模块10',
            name='devops',
            type=NUMBER,
            required=True,
            value=1
        ),
        input(
            label='运维',
            name='ops',
            type=NUMBER,
            required=True,
            value=0
        ),
     ])


    issue_analyse_2 = input_group('缺陷分析-致命&严重',[
        input(
            label='模块1',
            name='portal',
            type=NUMBER,
            required=True,
            value=1,
            help_text='请填写致命+严重的缺陷数'
        ),
        input(
            label='模块2',
            name='cmp',
            type=NUMBER,
            required=True,
            value=1
        ),
        input(
            label='模块3',
            name='nova',
            type=NUMBER,
            required=True,
            value=1
        ),
        input(
            label='模块4',
            name='net',
            type=NUMBER,
            required=True,
            value=1
        ),
        input(
            label='模块5',
            name='storage',
            type=NUMBER,
            required=True,
            value=1
        ),
        input(
            label='模块6',
            name='common',
            type=NUMBER,
            required=True,
            value=1
        ),
        input(
            label='模块7',
            name='product',
            type=NUMBER,
            required=True,
            value=1
        ),
        input(
            label='模块8',
            name='RDS',
            type=NUMBER,
            required=True,
            value=1
        ),
        input(
            label='模块9',
            name='arch',
            type=NUMBER,
            required=True,
            value=0
        ),
        input(
            label='模块10',
            name='devops',
            type=NUMBER,
            required=True,
            value=0
        ),
        input(
            label='运维',
            name='ops',
            type=NUMBER,
            required=True,
            value=0
        ),
     ])

    result_info = input_group('测试结论和建议',[
        textarea(
            label='测试结论',
            value='根据测试数据分析,当前版本缺陷总数较多,严重缺陷相对较少,所有缺陷均已解决,版本质量良好。',
            help_text='例:根据测试数据分析,当前版本缺陷总数较多,严重缺陷相对较少,所有缺陷均已解决,版本质量良好。',
            name='res',
            type=TEXT,
            required=True,
            rows=3,
            ),
        input(
            label='测试建议',
            value='继续加强代码review管控,模块7设计评审。',
            help_text='例:继续加强代码review管控,模块7设计评审。',
            name='suggest',
            type=TEXT,
            required=True,
            ),
        select(
            label='发布结果',
            value='可以发布',
            options=['可以发布','不可发布'],
            name='fb',
            required=True,
            ),
        input(
            label='缺陷主要原因',
            name='reason',
            type=TEXT,
            value='模块7设计缺失、需求不完整、代码BUG等导致',
            required=True,
        ),
    ])

    # =============== xlsx转换为html table================
    files = file_upload("请上传未解决的缺陷文件.xlsx", accept=['.xlsx','xls'],)


    pf = pd.read_excel(files.get('content'))


    import time
    times=time.strftime('%Y-%m-%d  %T' , time.localtime())
    test_time = '测试时间'

    # ===============Part One=======================
    put_markdown(f"# Python研究所 {basic_info['version1']} 版本 {basic_info['version2']} 测试报告").style('display:flex;justify-content: center;')
    put_markdown(f"## 1、概述")
    put_markdown(f"本测试报告为Python研究所{basic_info['version1']}版本功能测试报告;本报告目的在于评估{basic_info['version1']}/{basic_info['version2']}的基本功能是否满足发布要求。")
    put_markdown("本报告预期参与人员包括测试执行人员、测试负责人员、项目管理人员、模块10人员和其他开发团队成员。")

    # ===============Part Two=======================
    put_markdown("## 2、测试参考文档")
    put_column([
        put_markdown("- [x] 需求文档"),
        put_markdown("- [x] 概要设计"),
        put_markdown("- [x] 详细设计"),
        put_markdown("- [x] 环境部署文档"),
        put_markdown("- [x] 自测报告"),
        put_markdown("- [x] 转测申请"),
        put_markdown("- [x] 测试计划"),
        put_markdown("- [x] 测试用例"),
    ]
    )

    # ===============Part Three=======================
    put_markdown("## 3、测试设计")
    put_markdown("### 3.1、测试环境与配置")
    put_column(
    put_table([
    [span('Python研究所',row=2), span('测试环境', col=2)],
    ['环境地址','规模'],
    ['IaaS', 'Python研究所','3控+3管+4模块3'],
    ['Portal', 'Python研究所', 'xx'],
    ['模块2', 'Python研究所', 'xx'],
    ])).style('display:flex;justify-content: center;')

    put_markdown("### 3.2、测试用例设计")
    put_markdown("测试用例的设计按场景、模块等多个层次进行用例设计。通过基础核心功能、基础非核心功能以及异常场景下的功能可用性将用例分为了P0、P1、P2、P3四种级别。")
    put_markdown(" - P0:基础核心功能")
    put_markdown(" - P1:基础高频功能")
    put_markdown(" - P2:基础低频功能")
    put_markdown(" - P3:UI布局&用户体验")

    put_markdown("### 3.3、测试方法")
    put_markdown("本次测试采用黑盒测试方法。通过手动执行功能测试用例结合自动化回归进行测试。")

    # ===============Part Four=======================
    put_markdown("## 4、测试计划")
    put_markdown("### 4.1、测试时间和人员")
    put_markdown(f" >  测试时间:{basic_info['test_time1']}-{basic_info['test_time2']}")
    # put_table([
    #    ['模块','模块3','模块5','模块4','模块8','SLB','审计','组织','运营-模块7','运营-计费','运营-其他','模块1-其他'],
    #    ['主测试','Python研究所','Python研究所','Python研究所','Python研究所','phyger','Python研究所','Python研究所','Python研究所','Python研究所','Python研究所','all',],
    #    ['备测试','all','phyger','phyger','phyger','Python研究所','Python研究所','all','Python研究所','Python研究所','all','all']
    #     ])
    put_table([
        ['模块3', 'Python研究所', 'all'],
        ['模块5', 'Python研究所', 'phyger'],
        ['模块4', 'Python研究所', 'phyger'],
        ['模块8', 'Python研究所', 'phyger'],
        ['ok', 'phyger', 'Python研究所'],
        ['ko', 'Python研究所', 'Python研究所'],
        ['xx', 'Python研究所', 'all'],
        ['运营-模块7', 'Python研究所', 'Python研究所'],
        ['运营-计费', 'Python研究所', 'Python研究所'],
        ['运营-其他', 'Python研究所', 'all'],
        ['模块1-其他', 'all', 'all'],
        ], header=['模块', '主测试', '备测试'])

    put_markdown("### 4.2、功能覆盖")
    put_link(name="1. ★历史版本存量功能",url='https://app.rwork.crc.com.cn/mindnotes/bmnk9UIti5nJoDiBJN46F1w4DQb',new_window=True)
    put_html('<br>')
    put_link(name=f"2. ★{basic_info['version2']}新增需求",url='https://steam.crcloud.com/3/project/436/#/issue',new_window=True)


    put_markdown("### 4.3、被测环境结构")
    put_table([
        ['portal','aaaa','xxxxx'],
        ['middle','a+a','xx平台,xx和模块1的桥梁'],
        [span('模块1',row=13), 'xxx','xxx生命周期管理'],
        ['aa', 'aaaa'],
        ['xx', 'xxxx'],
    ],header=['分类','模块','概述'])

    put_markdown("### 4.4、测试用例")
    put_link(name='点击查看测试用例',url=basic_info['test_case_url'],new_window=True)

    # ===============Part Five======================
    put_markdown("## 5、缺陷分析")
    put_markdown("### 5.1、缺陷概览")
    put_markdown(f"致命缺陷数:{issue_info['die']}").style('color: red;')
    put_markdown(f"严重缺陷数:{issue_info['critical']}").style('color: #ff00ff;')

    # ===============Picture======================
    c = (
    Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
    .add_xaxis([
        '已拒绝',
        '已解决',
        '未解决',
        '缺陷总数'
    ])
    .add_yaxis("", [
        issue_info['refused'],
        issue_info['closed'],
        issue_info['error'],
        issue_info['issue_total']
    ])
    .set_global_opts(
        xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=0)),
        title_opts=opts.TitleOpts(title="缺陷-按状态分布"))
    )

    c.width = "100%"
    put_html(c.render_notebook())

    # ===============Picture======================
    c = (
    Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
    .add_xaxis([
        '致命',
        '严重',
        '一般',
        '建议'
    ])
    .add_yaxis("", [
        issue_info['die'],
        issue_info['critical'],
        issue_info['normal'],
        issue_info['low']
    ])
    .set_global_opts(
        xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=0)),
        title_opts=opts.TitleOpts(title="缺陷-按严重程度分布"))
    )

    c.width = "100%"
    put_html(c.render_notebook())
    # put_table([
    #     [issue_info['critical'], issue_info['closed'], issue_info['refused'],issue_info['fixed'],issue_info['error'],issue_info['issue_total']],
    # ], header=['严重缺陷', '已关闭','已拒绝','已修复','未解决','缺陷总数'])

    put_markdown(f"本次测试总共发现问题总计{issue_info['issue_total']}个,已经关闭{issue_info['closed']}个,关闭率为{float('%.2f' %(issue_info['closed']/issue_info['issue_total']*100))}%,还剩余{issue_info['error']}个缺陷未处理。缺陷发生的主要原因为:{result_info['reason']}。")


    put_markdown("### 5.2、缺陷分析")
    put_markdown("#### 5.2.1、缺陷-按模块分布")

    # ===============Bar Picture 缺陷分布 =================
    c = (
    Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
    .add_xaxis([
        '模块1',
        '模块2',
        '模块3',
        '模块4',
        '模块5',
        '模块6',
        '模块7',
        '模块8',
        '模块9',
        '模块10',
        'ops'
    ])
    .add_yaxis("总缺陷", [
        issue_analyse['portal'],
        issue_analyse['cmp'],
        issue_analyse['nova'],
        issue_analyse['net'],
        issue_analyse['storage'],
        issue_analyse['common'],
        issue_analyse['product'],
        issue_analyse['RDS'],
        issue_analyse['arch'],
        issue_analyse['devops'],
        issue_analyse['ops']
    ])
    .set_global_opts(
        xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-15)),
        title_opts=opts.TitleOpts(title="缺陷-按模块分布"))
    )

    c.width = "100%"
    put_html(c.render_notebook())

    # ===============Bar Picture 严重缺陷分布 =================
    put_markdown("#### 5.2.2、严重缺陷-按模块分布")
    c = (
    Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
    .add_xaxis([
        '模块1',
        '模块2',
        '模块3',
        '模块4',
        '模块5',
        '模块6',
        '模块7',
        '模块8',
        '模块9',
        '模块10',
        'ops'
    ])
    .add_yaxis("严重缺陷", [
        issue_analyse_2['portal'],
        issue_analyse_2['cmp'],
        issue_analyse_2['nova'],
        issue_analyse_2['net'],
        issue_analyse_2['storage'],
        issue_analyse_2['common'],
        issue_analyse_2['product'],
        issue_analyse_2['RDS'],
        issue_analyse_2['arch'],
        issue_analyse_2['devops'],
        issue_analyse_2['ops']
    ])
    .set_global_opts(
        xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-15)),
        title_opts=opts.TitleOpts(title="严重缺陷-按模块分布"))
    )

    c.width = "100%"
    put_html(c.render_notebook())

    put_markdown("#### 5.2.3、未修复的缺陷列表")
    put_html(pf.to_html(border=0))

    put_markdown("### 5.3、缺陷列表")
    put_link(name='点击查看缺陷列表',url=issue_info['issue_url'],new_window=True)

    put_markdown("## 6、测试结论和建议")

    put_markdown(f"{ result_info['res']}缺陷发生的主要原因为:{result_info['reason']}。")
    put_markdown(f"{ result_info['suggest']}")

    if result_info['fb']=='可以发布':
        put_markdown(f"当前版本质量状态:{ result_info['fb'] }").style('color: #00ff00')
    else:
        put_markdown(f"当前版本质量状态:{ result_info['fb'] }").style('color: #ff0000')

    #put_markdown(f"本次测试总共发现问题总计{issue_info['issue_total']}个,已经关闭{issue_info['closed']}个,关闭率为{float('%.2f' %(issue_info['closed']/issue_info['issue_total']*100))}%,还剩余{issue_info['error']}个缺陷未处理。缺陷发生的主要原因为:{issue_info['reason']}。")
    put_markdown("## 7、附录")
    put_markdown("### 7.1、缺陷状态定义")
    put_table([
        ['新建','测试人员测试过程中发现的,认为其可能会影响到系统功能,性能,用户体验等的问题,同时将其提出'],
        ['处理中','测试人员认为是系统缺陷或者是需要对系统进行优化,开发人员确认并且开始处理'],
        ['已修复','开发人员已经针对问题提出修复方案并且完成实施,自验通过'],
        ['已拒绝','问题的分析者认为不是缺陷,经问题提出者确认可拒绝'],
        ['关闭','缺陷确认者(一般为问题生成人)验证后认为问题已解决属实,并且回归通过'],
    ], header=['缺陷状态','状态释义'])
    put_markdown("### 7.2、缺陷严重程度定义")
    put_table([
        ['致命','确认或者基本确认会影响到系统核心功能或者系统稳定的问题'],
        ['严重','确认会影响到环境基础功能的问题'],
        ['一般','确认会影响到系统主要功能,或者在异常场景下会造成系统异常的问题'],
        ['建议','不影响系统功能,但影响系统的易用性(如界面美观问题、操作建议等)或产出物的一些非技术性质量问题(如文档版本、错别字等)'],
    ], header=['严重等级','等级释义'])

    put_text(f"Copyright © Phyger | Generate at:{times}").style('display:flex;justify-content: center;')

from pywebio.platform.flask import webio_view
from flask import Flask

app = Flask(__name__)

# `task_func` is PyWebIO task function
app.add_url_rule('/', 'webio_view', webio_view(ReportGenerate),
            methods=['GET', 'POST', 'OPTIONS'])  # need GET,POST and OPTIONS methods

if __name__=='__main__':

    app.logger.addHandler(handler)
    app.run(host='0.0.0.0', port=8989)

TPG 截图-输入

基础信息采集页面

缺陷信息采集页面

缺陷数据采集页面

致命严重缺陷信息采集页面

测试结论和建议采集

未解决问题excle上传

TPG 截图-输出

测试报告概览-1

测试报告概览-2

测试报告概览-3

测试报告概览-4

测试报告概览-5

结束语

这个工具非常简单,但是目前使用还是很方便的。同时它也有很多优化的空间,比如输入可以做成那种 form 表单,支持上一步数据保留,报告中的数据从测试系统自动拉取等等。期待 TPG 能够越来越好。