Unittest接口测试生成报告和日志方法

HTML报告

  1. 直接把HTMLTestRunner.py放入工程目录即可
  2. 报告脚本封装
#HTNL格式报告
    now = datetime.datetime.now().strftime('%Y-%m-%d_%H_%M_%S')
    htmlreport = reportpath + "/" + now + r"result.html"
    print("测试报告生成地址:%s"% htmlreport)
    fp = open(htmlreport, "wb")
    runner = HTMLTestRunner.HTMLTestRunner(stream=fp, 
    									   verbosity=2, 
    									   title="xxxx接口自动化测试报告", 
    									   description="用例执行情况")
    runner.run(case) # case为所有的测试用例
    fp.close()

LOG日志

  1. 使用Python自带的logging
  2. 直接引用即可
import logging
  1. log等级

  1. logging.basicConfig()函数包含参数说明

  1. logging模块中定义好的可以用于format格式字符串说明

  1. 生成log脚本封装
#LOG日志记录
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                        datefmt='%a, %d %b %Y %H:%M:%S',
                        filename=log_path + '/' + now + r"result.log",
                        filemode='w')
    logger = logging.getLogger()
    logger.info(case)

Unittest函数入口集成报告和日志

# coding=utf-8
import unittest
import time,datetime
from common import HTMLTestRunner
from common.send_mail import sendmain
import os
import logging

now_path = os.path.dirname(os.path.realpath(__file__)) # 获取当前路径

report_path = os.path.join(now_path , "../report") # HTML报告存储路径
log_path = os.path.join(now_path , "../log") # LOG日志存储路径

if not os.path.exists(report_path): os.mkdir(report_path)
case_path = os.path.join(now_path , "../case") # 测试用例路径


def load_case(casepath=case_path, rule="test*.py"):
    '''加载所有的测试用例'''
    discover = unittest.defaultTestLoader.discover(casepath, pattern=rule,) # 定义discover方法的参数
    return discover

def run_case(test_case, reportpath=report_path):
    '''执行所有的用例, 并把结果写入测试报告'''

    #HTNL格式报告
    now = datetime.datetime.now().strftime('%Y-%m-%d_%H_%M_%S')
    report = reportpath + "/" + now + r"result.html"
    print("测试报告生成地址:%s"% report)
    fp = open(report, "wb")
    runner = HTMLTestRunner.HTMLTestRunner(stream=fp, verbosity=2, title="xxxx接口自动化测试报告", description="用例执行情况")

    #LOG日志记录
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                        datefmt='%a, %d %b %Y %H:%M:%S',
                        filename=log_path + '/' + now + r"result.log",
                        filemode='w')
    logger = logging.getLogger()
    logger.info(test_case)

    # 调用load_case函数返回值
    runner.run(test_case)
    fp.close()
    time.sleep(2)
    sendmain(report, mail_to=['yyyyyy@qq.com'])
    print("发送测试报告邮件OK")

if __name__ == "__main__":
    my_cases = load_case()
    run_case(my_cases)
本文转载于网络 如有侵权请联系删除

相关文章

  • [Doris核心原理] -- FE启动过程原理分析3 -- 初始化Catalog

    本文承接上一篇[Doris核心原理]--FE启动过程原理分析2--启动类PaloFe.java,从上一篇中,我们了解了DorisFe启动类的运行过程,本篇主要讲解DorisFe启动时是如何初始化Catalog.java这个类的.Catalog.java是DorisFe的一个核心类,主要负责以下功能(包括但不限于)元数据初始化、管理等Load任务管理、清理等Export任务管理、清理等事物清理Editlog重放管理Fe角色变更管理回收站任务管理broker管理器资源管理器授权管理分桶数据统计管理动态分区任务管理启动类PaloFe.java是通过调用下面的代码进行初始化的.//initcatalogandwaititbeready Catalog.getCurrentCatalog().initialize(args); Catalog.getCurrentCatalog().waitForReady();复制接下来,我们主要讲解Catalog.getCurrentCatalog().initialize(args)的初始化过程.1.初始化配置的元数据目录信息.元数据目录需要事先创建,如果

  • C++矩阵运算

    矩阵的定义可以使用STL提供的Vector,譬如,定义A[4][4]1vector<vector<double>>A= 2{{1.0,T,0,0}, 3{0,1,0,0}, 4{0,0,1,T}, 5{0,0,0,1}};复制一、运算符重载实现矩阵加法1vector<vector<double>>operator+(vector<vector<double>>arrA,vector<vector<double>>arrB) 2{//矩阵加法 3//矩阵arrA的行数 4introwA=arrA.size(); 5//矩阵arrA的列数 6intcolA=arrA[0].size(); 7//矩阵arrB的行数 8introwB=arrB.size(); 9//矩阵arrB的列数 10intcolB=arrB[0].size(); 11//相乘后的结果矩阵 12vector<vector<double>>res; 13if((colA!=colB)||(rowA!=ro

  • lnstat命令显示Linux系统的网路状态

    lnstat命令实际上是读取系统“/proc”中目录“/proc/net/stat”下面的文件,来显示当前主机的网络状态的。lnstat命令是rtstat命令的更新替代命令,功能更完善。语法格式: lnstat[参数]常用参数:-c指定显示网络状态的次数,每隔一定时间显示一次网络状态-d显示可用的文件或关键字-i指定两次显示网络状的间隔秒数-k只显示给定的关键字-s是否显示标题头-w指定每个字段所占的宽度-h显示帮助信息-v显示指令版本信息参考实例显示网络状态:[root@linux~]#lnstat复制显示命令支持的统计文件:[root@linux~]#lnstat-d复制过滤出只想要查看的关键字段信息:[root@linux~]#lnstat-karp_cache:entries,rt_cache:in_hit,arp_cache:destroys arp_cach|rt_cache|arp_cach| entries|in_hit|destroys| 6|0|2|复制

  • 针对目前大部分读者是在校学生,因此特地写这篇文章,如果你购买一次iPad/Mac省个几百块钱。

    文章目录前言I、找到在线咨询入口,获取优惠链接II、其他购物细节 2.1免费镌刻信息2.2部分银行免息分期(花呗/招商)seealso:其他日常实用冷知识 自定义短语(输入码)博客同步技巧other前言针对目前大部分读者是在校学生,因此特地写这篇文章,如果你购买一次iPad/Mac省个几百块钱。我今天特地看了下新款iPad如果是学生可以省150.具体流程如下:I、找到在线咨询入口,获取优惠链接在下单界面,添加购物车按钮底部找到在线客服入口 获取优惠链接iPad便宜了150 II、其他购物细节2.1免费镌刻信息-----当然还可以免费写礼品赠言2.2部分银行免息分期(花呗/招商)包括常用的花呗以及招商 seealso:其他日常实用冷知识自定义短语(输入码)iPhone自定义键盘输入码(快速打出常用文字,类似Xcode的代码块)用法:输入短语,点击空格键,就自动填充内容用法:输入短语,点击空格键,就自动填充内容 博客同步技巧【现csdn正常更新文章】 原文:https://kunnan.blog.csdn.net/article/details/110491652

  • 致趣百川创始人何润入选「2020中国ToB行业年度榜单·领军人物」TOP10!|腾讯SaaS加速器·学员动态

    来源| 腾讯SaaS加速器二期项目-致趣百川1月6日,ToB行业头条联手3W集团正式发布了《2020中国ToB行业年度榜单·领军人物》。经过三个月的筹备和评选,致趣百川创始人&CEO何润(腾讯SaaS加速器学员)从数百位候选人中脱颖而出,荣登TOP10榜单。 何润硕士毕业于清华电子系,在埃森哲拥有近10年ERP及物联网项目经验。就职时趣互动期间,负责推动软件和数据驱动营销的整合解决方案落地,帮助联合利华中国某B2B业务线实现全球最快增长率。2016年创办致趣百川,致力于为企业提供以“内容+获客+线索孵化+销售跟进”为核心的B2B一站式营销云解决方案。2018年出版《获客》一书,广受业界好评。《中国ToB行业年度榜单》由ToB行业头条发起,自2018年开始每年举办,迄今已连续举办三年。活动主要关注ToB行业的新产品、新公司、新趋势,重视长期价值、深入行业研究,如今已成为企业参与较广、行业影响力较强的系列榜单之一。致趣百川是一家提供一站式营销云软件解决方案的企业,聚焦于科技、医疗、工业制造业等B2B行业,一站式营销云包含SCRM(社交营销)、CMS(内容管理)、EMA(活动管理)、

  • Python项目实战:爬取每一个歌单中的歌曲列表

    今天为大家介绍一个爬取网易云音乐每一个歌单中的歌曲汇总,你想听的歌它都有,利用简单的爬虫库BeautifulSoup来进行获取网站的信息,下面一起来看看吧导入第三方库获取网页定义数组头部信息解析音乐信息开始扫描歌单开始保存歌单批量保存成功(http://upload-images.jianshu.io/upload_images/13090773-3970acea75b0e6b5?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)运行结果![][(http://upload-images.jianshu.io/upload_images/13090773-011516d0b750426f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

  • 第九章:舞台(Stage)简介舞台类以及使用

    功能很重要,用于演员的盛装和管理。简介1.API定义:拥有多个层次结构的二维场景,场景中有许多演员,处理纹理图片和接收的输入事件,以及负责操作游戏视角、 2.特点:盛装演员,监听并接收触屏事件,处理GroupActors。 3.提醒:Stage本身封装了一个Camera(相机),用于适配不同分辨率设备。以及封装了一个精灵类(SpriteBatch) 特点: 1.Stage可以铺满屏幕,设置视角大小,同时设置照相机 2.调配Actor,Group与Screen之间的关系转换,一个Stage必须负责接收输入事件,同时,它将自动分配给演员(Actor)。 3.通常是调用Gdx.input.inputProcessor=stage来实现监听注册舞台类以及使用publicStage(floatwidth,floatheight,booleankeepAspectRatio,SpriteBatchbatch)复制1.width舞台宽度 2.height舞台高度 3.keepAspectRatio设置舞台是否铺满屏幕,true则不铺满,false则铺满。默认false铺满。 4.传入声明的精灵类。常

  • Git学习笔记.

    Git的工作就是创建和保存你项目的快照及与之后的快照进行对比Git与SVN区别GIT不仅仅是个版本控制系统,它也是个内容管理系统(CMS),工作管理系统等。如果你是一个具有使用SVN背景的人,你需要做一定的思想转换,来适应GIT提供的一些概念和特征。Git与SVN区别点:1、GIT是分布式的,SVN不是:这是GIT和其它非分布式的版本控制系统,例如SVN,CVS等,最核心的区别。2、GIT把内容按元数据方式存储,而SVN是按文件:所有的资源控制系统都是把文件的元信息隐藏在一个类似.svn,.cvs等的文件夹里。3、GIT分支和SVN的分支不同:分支在SVN中一点不特别,就是版本库中的另外的一个目录。4、GIT没有一个全局的版本号,而SVN有:目前为止这是跟SVN相比GIT缺少的最大的一个特征。5、GIT的内容完整性要优于SVN:GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。一般工作流程如下:克隆Git资源作为工作目录。在克隆的资源上添加或修改文件。如果其他人修改了,你可以更新资源。在提交前查看修改。提交修改。在修

  • WCF技术剖析之二十: 服务在WCF体系中是如何被描述的?

    任何一个程序都需要运行于一个确定的进程中,进程是一个容器,其中包含程序实例运行所需的资源。同理,一个WCF服务的监听与执行同样需要通过一个进程来承载。我们将为WCF服务创建或指定一个进程的方式称为服务寄宿(ServiceHosting)。服务寄宿的本质通过某种方式,创建或者指定一个进程用以监听服务的请求和执行服务操作,为服务提供一个运行环境。服务寄宿的方式大体分两种:一种是为一组WCF服务创建一个托管的应用程序,通过手工启动程序的方式对服务进行寄宿,所有的托管的应用程序均可作为WCF服务的宿主,比如Console应用、WindowsForms应用和ASP.NET应用等,我们把这种方式的服务寄宿方式称为自我寄宿(SelfHosting)。另一种则是通过操作系统现有的进程激活方式为WCF服务提过宿主,Windows下的进程激活手段包括IIS、WindowsService或者WAS(WindowsProcessActivationService)等。服务寄宿的手段是为一个WCF服务类型创建一个ServiceHost对象(或者任何继承于ServiceHostBase的对象)。无论采用哪种寄宿方

  • .Net版SQLite无法访问网络位置的数据库文件-winOpen,os_win.c 36702异常

    最近一个C#小程序,希望将SQLite数据库放在网络共享的位置,让多个客户端同时访问。却发现SQLite连接不上该网络位置的数据库,而如果数据库在本地则一切正常。 例如将SQLite数据库test.dat放在共享位置:\\System\Data\test.dat, 通过SQLite创建数据库连接,执行Open时,将抛掷异常: SQLiteerror(14):os_win.c:36702:(3)winOpen(D:\System\Data\test.dat)-系统找不到指定的路径。 SQLiteerror(14):os_win.c:36702:(3)winOpen(D:\System\Data\test.dat)-系统找不到指定的路径。 SQLiteerror(14):cannotopenfileatline36711of[9491ba7d73] “System.Data.SQLite.SQLiteException”类型的第一次机会异常在System.Data.SQLite.dll中发生 System.Data.SQLite.SQLiteException(0x80004005):u

  • Day17.1:静态与非静态的详解

    静态与非静态 静态方法——类方法 是以static为关键词,从属于类,与类共生 publicclassStudents{//class修饰的是一个类,所以这是一个学生类 publicstaticvoidsay(){//方法前加static修饰,则是静态方法,在其他类中可以直接调用 System.out.println() } }//在下面的类中直接调用 复制 publicclassDemo{ publicstaticvoidmain(String()args){ Students.say();//调用上方中类的方法;因为是静态方法;直接类名.方法即可; //只要一个类的方法是静态方法,不管这个类是是否在本类中,调用静态方法时,通过类名.方法即可调用! } } 复制 非静态方法——实例方法,成员方法 对象专用方法,与对象共生 publicclassStudents publicvoidsay(){//方法前没有static修饰,则是非静态方法,在其他类中可以不能直接调用!不能直接调用!不能直接调用! System.out.println() } }//在下面的类中进行

  • 万能的数据同步工具DATAX,DataX,datax_web 下载安装

    DataXDataX是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括MySQL、SQLServer、Oracle、PostgreSQL、HDFS、Hive、HBase、OTS、ODPS等各种异构数据源之间高效的数据同步功能。 FeaturesDataX本身作为数据同步框架,将不同数据源的同步抽象为从源头数据源读取数据的Reader插件,以及向目标端写入数据的Writer插件,理论上DataX框架可以支持任意数据源类型的数据同步工作。同时DataX插件体系作为一套生态系统,每接入一套新数据源该新加入的数据源即可实现和现有的数据源互通。 https://github.com/alibaba/DataX/blob/master/userGuid.md     DataX,datax_web下载安装 https://github.com/alibaba/DataX 1.DownloadDataX下载地址https://datax-opensource.oss-cn-hangzhou.aliyuncs.com/20220530/datax.tar.gz 2.dat

  • 物联网架构成长之路(59)-SpringBoot项目作为系统应用,自启动

    一、前言   开发完项目,一般调试都是在eclipse或者idea上运行服务的。但是要部署到服务器上,就需要后台运行和自启动等配置了。这里采用Debian系统作为演示。   二、后台运行   工程项目代码采用maven进行打包。 1mvnwpackage复制   打包后,生成一个jar包   服务器安装jre运行环境后,执行 1java-jar***.jar复制   后台执行命令nohup&   控制台打印日志与错误日志,采用重定向 >/dev/null2>dev/null   cat kingdee-sync   1#!/bin/sh 2JAR_NAME=/opt/KingdeeSync/SaleKingdeeOutstockSync-0.0.1-SNAPSHOT.jar 3do_start() 4{ 5nohupjava-jar$JAR_NAME--spring.profiles.active=prod>/dev/null2>/dev/null& 6echo"============START

  • 【小程序】微信公众号模板消息跳转小程序发送失败:errcode=40013 , errmsg=invalid appid rid:

    当我发送公众号的模板消息 该模板消息的作用是点击跳转小程序 报错: errcode=40013,errmsg=invalidappidrid:   解决: 公众号没有和小程序进行关联,关联一下   十年开发经验程序员,离职全心创业中,历时三年开发出的产品《唯一客服系统》 一款基于Golang+Vue开发的在线客服系统,软件著作权编号:2021SR1462600。一套可私有化部署的网站在线客服系统,编译后的二进制文件可直接使用无需搭开发环境,下载zip解压即可,仅依赖MySQL数据库,是一个开箱即用的全渠道在线客服系统,致力于帮助广大开发者/公司快速部署整合私有化客服功能。 开源地址:唯一客服(开源学习版) 官网地址:唯一客服官网

  • 操作系统面试题整理

    1.操作系统进程与线程的区别,线程的几种状态 进程:资源分配的基本单元 线程:任务调度的基本单元 进程包括:1.占有的资源(内存,打开的文件,访问的网络) (2)上下文  线程执行过程中共享资源,独占上下文 线程/进程状态: 就绪运行阻塞 挂起的标志就是换出到外存,在外存的进程肯定是不能执行的,所以挂起的目的就很明显,在内存资源不足时,需要把一些进程换出到外存,给着急运行的进程腾地方。 2.僵尸进程和孤儿进程的区别 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作 僵尸进程:当进程退出父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸进程。僵尸进程会在以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。僵尸进程会造成资源浪费。 父进程调用wait()阻塞自己,等待子进程退出 fork()和wait()成对出现 3.进程的调度算法 时间片轮转 优先权调度算法:抢占式(分时系统linux)和非抢占式

  • nim也玩一行流,nim版的list comprehension

    nim是一门风格类似python的静态编译型语言,官方网站:http://nim-lang.org 如果你想折腾nim的编辑环境,可以用sublimetext3+插件nimlime,notepad++,atom也有相应的插件,想折腾可以自己尝试,这里先不细说。 我最近想入门nim,用nim实现projecteuler的解题是个很好的练习方式,那么从第一题开始吧: projecteuler第一题链接:https://projecteuler.net/problem=1 题目大意是:计算1000以内是3或者5的倍数的总和。 看了一些教程加上查了查官方文档,写出下面的nim程序: #https://projecteuler.net/problem=1 varresult:int=0 forrin1..999: if(rmod3)==0or(rmod5)==0: result+=r echoresult 复制 像这样的简单的题目,很多人都贴了pythonrubyhaskell之类语言的一行流的代码。nim中有没有listcomprehension呢? 搜索了一下,还真有。(官方文档:)[ht

  • 小贴士--Python

      1.查看python安装好的包版本信息:piplist   原贴,有空完善。http://yangzb.iteye.com/blog/1824761   2.Python文件快速执行。   加头文件快速执行Python文件,如果你用 pythontest.py 来运行,那么写不写都没关系,如果要用 ./test.py 那么就必须加这行,这行被称为shebang,用来为脚本语言指定解释器。   头文件指定运行的Python 一般是:#!/usr/bin/python,不太清楚的同学可以通过whichPython查看。   3.一般机器没有admin或者其他权限,只能安装到自己文件夹目录下时使用的命令,注意后面的参数!!      4.安装了anconda之后,可以直接通过condasearch命令来安装包,但是需要连接外网。   condainstall来实际安装   5.查看一个变量可以使用的方法:dir(variant)、查看变量类型:type()   6.split()

  • 数据库规约解读

    适用场景:并发量大、数据量大的互联网业务 基础规范 1、必须使用InnoDB存储引擎 解读:支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高 2、新库默认使用utf8mb4字符集 解读:utf8mb4和utf8都是万国码,无需转码,无乱码风险。其中utf8mb4是utf8的超集,emoji表情以及部分不常见汉字在utf8下会表现为乱码,故需要升级至utf8mb4。 3、数据表、数据字段必须加入中文注释 解读:N年后没谁知道这个r1,r2,r3字段是干嘛的。 不过也有人提出,加入注释会方便黑客,建议“注释写在文档里,文档和数据库同步更新”。 这个建议根据经验来说是不太靠谱的: (1)不能怕bug就不写代码,怕黑客就不写注释,对吧? (2)文档同步更新也不太现实,还是把注释写好,代码可读性做好更可行,互联网公司的文档管理?呆过互联网公司的同学估计都清楚 4、禁止使用存储过程、视图、触发器、Event 解读:军规的背景是“并发量大、数据量大的互联网业务”,这类业务架构设计的重点往往是吞吐量,性能优先(和钱相关的少部分业务是一致性优先),对数据库性能影响较大的数据库特

  • ReentrantLock(公平锁、非公平锁)可重入锁原理

    基本使用 ReentrantLock,位于java.util.concurrent包,于JDK1.5引入,一种可重入互斥Lock,其基本行为和语义与使用synchronized方法和语句访问的隐式监视器锁相同,但具有扩展功能。 ReentrantLock的使用也很简单,在源码注释中可以看到使用的推荐方式: publicvoidm(){ lock.lock();//blockuntilconditionholds try{ //...methodbody }finally{ lock.unlock() } } 复制 具体使用代码如下: packagecom.starsray.test.lock; importjava.util.concurrent.CountDownLatch; importjava.util.concurrent.locks.ReentrantLock; publicclassTestReentrantLock{ privatefinalstaticReentrantLocklock=newReentrantLock(); staticintthreadCou

  • 数据结构专题-学习笔记:Link Cut Tree 动态树

    目录1.前言2.LCT2.1实链剖分2.2LCT存储方式2.3基础函数2.4Access2.5MakeRoot2.6FindRoot2.7Split2.8Link2.9Cut2.10总体代码3.参考资料 1.前言 LinkCutTree动态树,简称LCT,是一种维护动态森林的数据结构。 前置知识:Splay。 2.LCT 例题:P3690【模板】动态树(LinkCutTree) 2.1实链剖分 实链剖分也是一种对树的剖分方式,类似于重链剖分和长链剖分,其将一棵树上的边,随意划分成实边和虚边,每个点到其孩子的所有连边中至多只有一条实边,别的边全都是虚边,即孩子父亲单实边。 注意:实链剖分中我们对边的划分是任意的,并没有任何的限制条件,而且我们可以随时转换实虚边,只要保证一次操作(不是转换)后依然保证孩子父亲单实边即可。 规定:下文中所有图片,实边是实线,虚边是虚线。 实际上LCT就是将这棵树划分成若干条实链和虚链,然后将每一条实链用一棵Splay维护有关信息,对于每一条实链,Splay的中序遍历是按照深度递增的点的排列。 (接下来的图都是从YangZhe的论文上找来的) 比如说下面这棵树

  • 【线段树】【积累】主席树杂题积累 2016CCPC长春K SequenceII

    主席树杂题积累2016CCPC长春KSequenceII 2016CCPC长春KSequencII 题意: 给定\(n,m\),一个长度为\(n\)的数组\(a\),\(m\)个询问。每个询问给出\(l,r\)要求输出第\(\lceil{\frac{k}{2}}\rceil\)个在区间\([l,r]\)第一次出现的元素的位置。其中\(k\)是区间\([l,r]\)中,不同元素的个数。 \(1\leql\leqr\leqn\leq2\times10^5\),\(0\leqa_i,m\leq2\times10^5\)。 有\(T\)的测试样例,\(T\leq2\)。 解: 建主席树,这里的主席树是指静态求第k小的那种主席树,即动态开点权值线段树。 从\(n\)到\(1\)建主席树。以下标为权值线段树的权值区间。 若对于当前的点\(i\),\(a_i\)在此之前没有出现过(即\(\forall\,a_j[j>i],a_i\nea_j\)),则对当前点的线段树,权值位置\(i\)的值\(+1\),否则,记\(a_i\)在此前最后一次出现的位置为\(last_{a_i}\),另建一个棵树(

相关推荐

推荐阅读