白盒测试

测试理论回顾:

黑盒测试:是针对整个产品系统进行的测试,验证系统是否满足需求规格的定义,及软件产品的正确性和性能等是否满足其需求规格的要求。
灰盒测试:是介于白盒测试与黑盒测试之间的一种测试,灰盒测试多用于集成测试阶段,不仅关注输出、输入的正确性,同时也关注程序内部的情况。
白盒测试:是通过程序的源代码进行测试而不使用用户界面。这种类型的测试需要从代码内部在算法,路径,条件等等中的缺点或者错误,进而加以修正。

单元测试:

单元测试是针对程序的最小单元来进行正确性检验的过程。单元:一个单元可能是单个程序、类、对象、方法(函数)等。 优点是可以减少BUG;快速定位BUG;提高代码质量;减少调试时间。缺点是周期时间长;耗费资源(主要是人力资源);能力要求高。

一、单元测试流程

    1. 单元测试-计划:1) 确定要测试代码范围;2) 评估标准(确定被测代码的覆盖率)
    2. 测试策略-设计:1) 拿到开发代码进行调整(保证单元间可独立执行)
    3. 测试策略-实现:1) 根据调整好的代码-画流程图;2) 根据流程图画流图-确定复杂度、路径;3) 根据复杂度和路径确定测试用例(测试数据)
    4. 单元测试-执行:1) 使用测试框架(UnitTest)编写单元测试用例;2) 测试用例(代码)和测试数据分离;3) 生成测试报告

二、单元测试-计划

确定要测试代码以及确定这些被测代码的评估标准、优先级等说明:1. 确定单元测试范围(哪些代码要做单元测试);评估标准-(被测代码的逻辑覆盖率)

1.1、如何确定20%代码
确定单元测试的代码测试范围,我们依据:二八原则(20%的代码中隐藏了80%的缺陷)
    1. 频率:使用频率高的代码段;
    2. 复用性:(是否已被复用,是否被别的地方引用,如果被别的地方调用这个函数,没问题,基本不用测) 
        1). 全新(指没有被复用的代码)
        2). 部分复用
        3). 全部复用
    3. 开发人员:   
        1). 技术(指由技术水平不那么高的开发人员写的代码)
        2). 业务(指由对业务不那么熟悉的开发人员写的代码)
    4. 复杂度:业务逻辑复杂度(一般认为圈复杂度在7级以上代码包括在20%的代码中)
最后,我们会得到一张表,来列名我们要测的代码范围:

测试范围

1.2、评估标准(覆盖率)如何确定逻辑覆盖率
例如,我们拿到一段代码,实现的一个需求,我们首先要画流程图(使用统一规定标准图形,描述程序运行的具体步骤),然后以此来确定覆盖率,并且以后可以根据流程图画流图
    1. 语句覆盖率,说明:非分支非判断的代码,覆盖率计算方式:被覆盖语句/总语句,例如,流程图里有3个语句,对于一个测试数据,它能覆盖2个语句,则语句覆盖率为2/3
    2. 分支覆盖率,说明:判断语句的分支,例如if判断有两个分支,覆盖率计算方式:覆盖分支/总分支数,例如流程图里的if判断有2个分支,对于一个测试数据,它能覆盖1个分支,则分支覆盖率为1/2
    3. 条件覆盖率,说明:一个条件有两个结果 true、false,所以一个条件分母为2,两个条件分母为4...例如:if username=='admin' and pwd=='123456':这里就有两个条件,分母为4
        注意:条件之间使用逻辑符 and连接,第一个条件如果失败,不会在去判断第二个条件,如果测试数据是username==ad, pwd就不会判断,覆盖率为1/4;如果为or第一个条件失败回去判断第二条件
    4. 路径覆盖率,说明:路径就是从开始-到结束的过程, 覆盖率计算方式:覆盖路径/全部路径
        注意:路径的分子永远为1, 有时候看流程图路径有4条,但有一条永远不可成立,所以实际路径为3条,计算路径覆盖率时分母就为3
    5. 分支条件覆盖率,说明:分支和条件的组合,1.分子=分支覆盖率的分子+条件覆盖率的分子;2.分母=分支覆盖率的分母+条件覆盖率的分母

三、单元测试策略-设计

单元测试策略:针对单元测试选择测试的一种方式;
单元测试策略方式:
    1. 自上到下,方式:从最上层函数往下开始逐层测试,缺点(成本高)
    2. 自下到上,方式:从最底层函数往上开始逐层测试,缺点(周期长,需要开发写完所有代码才能开始测试)
    3. 孤立策略【推荐使用】方式:选择需要进行测试的函数进行测试,优点:选择重要的代码进行测试,测试构成中免不了测的某些函数会调用别的函数,所以一定要学会打桩

注意:打桩-->打桩就是模拟编写一个我们需要引用的函数,模拟定义被调用的函数名,提示:一般我们只模拟写个函数名,直接返回相应的结果即可(return 结果;pass)
示例:def fun_1(self):
                return true

四、单元测试策略-实现

测试策略实现:把我们选定的代码,而且保证选定的代码能独立运行(已经打完桩),转向流程图、流图及用例的过程
测试策略实现如何操作:1. 将测试代码转换成流程图;2. 根据流程图转换为流图,有了流图测试用例就出来了


流程图

什么是流图?
概念:表达程序业务逻辑的复杂度一种示意图
构成:
    1) 圈:判断条件(一个条件一个圈)、语句块(一条或多条语句,挨着的语句可以用一个圈表示)两者都圈   
    2) 线:箭头指向的线,用来连接圈和圈的指向   
流图的目的:
    1) 确定单元的复杂度级别
    2) 确定测试用例
    备注: 路径的个数为复杂度的级别(一条路径为1个复杂度级别),有N个条件最少有N条路径,最多N+1条路径

流图

* 路径:2 (1-2-3,1-3);复杂度:2;用例个数:2

根据流图写的测试用例

五、单元测试——执行

通过单元测试框架对要进行测试代码的实践过程

需求:通过Python语言编写一个运算的类(Calc),类中包含两个函数:add(self,a,b) 返回a+b之和;sub(self,a,c) 返回a-c之差
    - 导包 UnitTest 、Calc类
    - 新建单元测试类 Test01(集成 unitTest.TestCase)
    - 新建testAdd()函数
        - 导入Calc类中的add函数
        - 添加断言
    - 新建testSub()函数
        - 导入Calc类中的sub函数
        - 添加断言
    - 执行测试:unittest.main()

import unittest
# 导入要测试的 Calc类
from UT.Day02.Code.test01_lx01import Calc
class Test_Calc(unittest.TestCase):
    def setUp(self):
        print("setUp被执行")
    def tearDown(self):
        print("tearDown被执行")
    def test_add(self):
        result=Calc().add(10,10)
        print("test_add方法被执行")
        self.assertEqual(result,20)
    def test_sub(self):
        result=Calc().sub(10,20)
        print("test_sub方法被执行")
        self.assertEqual(result,-10)

if __name__== '__main__':
    # main运行当前类中所有test开头的方法
    unittest.main()

数据直接写入代码中,如果数量庞大,我们需要频繁改动数据或复制冗余代码进行实现数据测试目的。做不到数据分离(代码和数据分开),不便维护

参数化

根据需求动态获取数据并进行赋值的过程
参数化方式:XML格式(1); CSV格式(2);JSON串 (3);TXT文本(4)
提示:括号内为推荐使用优先级,从小到大;1-2为扩展了解,3-4建议重点了解 

XML格式:

XML是一种标记语句,很类似HTML标记语言;后缀 .xml;XML是传输和存储数据,焦点在数据;HTML是显示数据,焦点在外观;XML不适合大量参数化数据时使用

XML格式:
<?xml version="1.0" encoding="UTF-8"?>
<book category="面授">
    <title>单元测试</title>
    <author>XST</author>
    <year>2008</year>
    <price>39.95</price>
</book>

1. 必须有XML声明语句:<?xml version="1.0" encoding="UTF-8"?>
2. 必须要有一个根元素,如:<book>
3. 标签大小写敏感
4. 属性值用双引号
5. 标签成对
6. 元素正确嵌套
7. 标签名可随意命名,但有以下限制(注意:命名允许重复)
    1) 不能以数字或者标点符号开始参
    2)不能以字符 “xml”(或者 XML、Xml)开始
    3) 名称不能包含空格

需求:对三角形案例单元测试使用XML格式进行参数化

被测代码段:
class Sjx(): def sjx(self,a,b,c):
     # 判断是否为三角形
     if a+b>c and a+c>b and b+c>a:
         # 判断是否为等边三角形
         if a==b and b==c:
             return "等边三角形"
         elif a==b or a==c or b==c:
             return "等腰三角形"
         else:
             return "普通三角形"
     else:
         return "不是三角形"
if __name__ == '__main__':
     print(Sjx().sjx(2,3,4))

--------------------------------------------------------------------------------------------------------------------------

操作步骤:1. 编写XML数据文件;2. 编写读取XML模块函数;3. 单元测试模块中引用XML读取函数;4. 执行

编写XML数据文件:
<?xml version="1.0" encoding="UTF-8" ?>
<SJX>
     <bian>
         <b1>2</b1>
         <b2>3</b2>
         <b3>4</b3>
         <expect>普通三角形</expect>
     </bian>
     <bian>
         <b1>2</b1>
         <b2>2</b2>
         <b3>2</b3>
         <expect>等边三角形</expect>
     </bian>
     <bian>
         <b1>2</b1>
         <b2>2</b2>
         <b3>3</b3>
         <expect>等腰三角形</expect>
     </bian>
     <bian>
         <b1>2</b1>
         <b2>3</b2>
         <b3>2</b3>
         <expect>等腰三角形</expect>
     </bian>
     <bian>
         <b1>3</b1>
         <b2>2</b2>
         <b3>2</b3>
         <expect>等腰三角形</expect>
     </bian>
     <bian>
         <b1>3</b1>
         <b2>2</b2>
         <b3>1</b3>
         <expect>不是三角形</expect>
     </bian>
     <bian>
         <b1>1</b1>
         <b2>2</b2>
         <b3>3</b3>
         <expect>不是三角形</expect>
     </bian>
     <bian>
         <b1>2</b1>
         <b2>3</b2>
         <b3>1</b3>
         <expect>不是三角形</expect>
     </bian>
</SJX>

编写读取XML模块函数
# 导包
minidomfrom xml.dom import minidom
class Read_Xml():
    def readXml(self,node,num,nodeChild):
         # 解析xml文档
         dom=minidom.parse("../DataPool/sjx.xml")
         # 获取文档对象
         root=dom.documentElement
         # 获取bian元素
         element=root.getElementsByTagName(node)[int(num)]
         # 获取指定bian元素 如b1
         return element.getElementsByTagName(nodeChild)[0].firstChild.data
     def get_len(self,node):
         # 解析xml文档
         dom=minidom.parse("../DataPool/sjx.xml")
         # 获取文档对象
         root=dom.documentElement
         # 获取bian元素
         return len(root.getElementsByTagName(node))
if __name__ == '__main__':
     print(Read_Xml().readXml("bian",0,"b3"))
     print(Read_Xml().get_len("bian"))

单元测试模块中引用XML读取函数;执行
# 导包 unittest、三角形函数、读取xml数据类
import unittest
from UT.Day02.Code.sjx import Sjx
from UT.Day02.ReadData.read_xml import Read_Xml
# 实例化三角形
sjxClass=Sjx()
# 实例化读取xml类
readXmlClass=Read_Xml()
class Test_Xml(unittest.TestCase):
     def test001(self):
         for i in range(readXmlClass.get_len("bian")):
             # 目的测试三角形程序
             result=sjxClass.sjx(int(readXmlClass.readXml("bian",i,"b1")),                                             int(readXmlClass.readXml("bian", i, "b2")),                                                                                int(readXmlClass.readXml("bian", i, "b3")))
             # 添加断言,判断三角形程序返回的结果是否符合我们预期结果             self.assertEqual(result,readXmlClass.readXml("bian", i, "expect"))             print(readXmlClass.readXml("bian",i,"b1"), readXmlClass.readXml("bian", i, "b2"),                     readXmlClass.readXml("bian", i, "b3"), readXmlClass.readXml("bian", i,                     "expect"),"--》验证通过")

重点分析:
1. 导入XML包 from xml.dom import minidom
2. 加载解析 dom=minidom.parse(filename)
3. 获取对象  root=dom.documentElement
4. 获取子元素 aas=root.getElementsByTagName(one)[0]
5. 获取子元素值 aas.getElementsByTagName(two)[0].firstChild.data

CSV格式:

CSV是一种以逗号做分割的表格格式; 后缀 .csv

使用CSV实现三角形案例参数化-操作步骤
1. 创建CSV文件
2. 编写CSV读取模块函数
3. 单元测试-引用CSV读取函数
4. 执行

创建CSV文件:
2,2,2,等边三角形2,2,3,等腰三角形2,3,2,等腰三角形3,2,2,等腰三角形1,2,3,不是三角形2,3,1,不是三角形3,2,1,不是三角形2,3,4,普通三角形

编写CSV读取模块函数:
# 导包
import csv
class Read_Csv():
    def readCsv(self):
         # 打开csv
         with open("../DataPool/sjx.csv","r",encoding='utf-8') as f:
             datas=csv.reader(f)
         # 新建空列表,把单行返回的列表添加成整体的列表
        lines=[]
        for data in datas:
             # 添加数组
             lines.append(data)
        return lines
if __name__ == '__main__':
     print(Read_Csv().readCsv())

单元测试-引用CSV读取函数;执行:  
# 导入unittest、三角形函数、csv读取参数类
import unittest
from UT.Day02.Code.sjx import Sjx
from UT.Day02.ReadData.read_csv import Read_Csv
# 实例化三角形
sjxClass=Sjx()
# 实例化读取csv工具
readCsvClass=Read_Csv()
class Test_Csv(unittest.TestCase):
     # 测试三角形函数程序
     def test001(self):
         for i in range(len(readCsvClass.readCsv())):             result=sjxClass.sjx(int(readCsvClass.readCsv()[i][0]),
                                           int(readCsvClass.readCsv()[i][1]),
                                           int(readCsvClass.readCsv()[i][2]))
             # 断言:三角新运行完后返回的结果和预期结果做对比             self.assertEqual(result,readCsvClass.readCsv()[i][3])
            print(readCsvClass.readCsv()[i][0], readCsvClass.readCsv()[i][1],                     readCsvClass.readCsv()[i][2], readCsvClass.readCsv()[i][3],"---》验证通过")
if __name__ == '__main__':
    unittest.main()

重点分析:
1. 导包 import csv
2. 打开csv文件:
    with open("../Data/sjx.csv","r",encoding="utf-8") as f:
        lines=csv.reader(f)

JSON串:

一种轻量级数据交换格式;后缀名 .json ;提示: 接口测试一般使用JSON为接口传递数据规范格式
格式:{"name":"张三","age":28};提示:由键值对组成,健名和值之间使用分号(:)分割,多个键值对之间使用逗号(,)分割

使用JSON实现三角形案例参数化-操作步骤
1. 编写JSON文件
2. 编写JSON读取模块函数
3. 单元测试-引用JSON读取函数
4. 执行

编写JSON文件 :
{"data": [ {"b1":2,"b2":2,"b3":2,"expect":"等边三角形"}, {"b1":2,"b2":2,"b3":3,"expect":"等腰三角形"}, {"b1":2,"b2":3,"b3":2,"expect":"等腰三角形"} ]}

编写JSON读取模块函数 :
# 导包 json
import json
# 打开文件流
class Read_Json():
    def readJson(self):
        with open("../DataPool/sjx.json","r",encoding="utf-8") as f:
         # 调用load()
         datas=json.load(f)
         # 返回字典data键名对应的值
         return datas["data"]
if __name__ == '__main__':
     print(Read_Json().readJson())

单元测试-引用JSON读取函数;执行 :
# 导包 unittest 、三角形函数程序、读取json类
import unittest
from UT.Day02.Code.sjx import Sjx
from UT.Day02.ReadData.read_json import Read_Json
# 实例化三角形
sjxClass=Sjx()
# 实例化读取JSON类
readJsonClass=Read_Json()
class Test_Json(unittest.TestCase):
    # 测试三角形
    def test001(self):
        for i in range(len(readJsonClass.readJson())):
            # 调用三角形函数
            result=sjxClass.sjx(int(readJsonClass.readJson()[i]["b1"]),                                                                                       int(readJsonClass.readJson()[i]["b2"]),                                                                                       int(readJsonClass.readJson()[i]["b3"]))
            # 断言 三角形返回的结果是否与预期结果相符             self.assertEqual(result,readJsonClass.readJson()[i]["expect"])
           # 打印运行参数及结果
            print(readJsonClass.readJson()[i]["b1"], readJsonClass.readJson()[i]["b2"],                     readJsonClass.readJson()[i]["b3"], readJsonClass.readJson()[i]["expect"],"--->                        通过!")
if __name__ == '__main__':
    unittest.main()

难点分析
1. 导入JSON包(import JSON)
2. 打开JSON文件并解析
    with open('../DataXML/sjx.json','r',encoding='utf-8') as f:
        file=json.load(f)

TXT文本:

一种纯文本格式; 后缀名 .txt;TXT文本优点: 编写测试数据方便;使用模块函数读取时便捷

使用TXT实现三角形案例参数化-操作步骤
1. 创建txt文本并写入测试数据
2. 编写读取txt模块函数
3. 单元测试-引用JSON读取函数
4. 执行

创建txt文本并写入测试数据:
第一条边,第二条边,第三条边,预期结果
2,2,2,等边三角形
2,2,3,等腰三角形
2,3,2,等腰三角形
3,2,2,等腰三角形
1,2,3,不是三角形
2,3,1,不是三角形
3,2,1,不是三角形
2,3,4,普通三角形

编写读取txt模块函数 :
class Read_Txt():
    def readTxt(self):
        # 打开txt文件流
        with open("../DataPool/sjx.txt","r",encoding="utf-8") as f:
            # 通过文件流调用读取的方法读取数据--->所有行
            datas=f.readlines()
        # 新建列表 --》添加分割后的单行列表数据
        lines=[]
        # 遍历
        for data in datas:
            '''
                strip():去除字符串前后回车,空格,tab键 
                split():使用指定符号进行分割字符串并以列表格式返回分割后的数据
            '''
            # 把分割后的单行列表数据添加到整体的列中,并返回
            lines.append(data.strip().split(","))
        # 返回数据 [1:] 返回从下标1开始返回数据
        return lines[1:]

 '''
    f.read() #读取所有数据
    f.readline() # 读取单行
    f.readlines() # 读取所有行
'''
if __name__ == '__main__':
     print(Read_Txt().readTxt())

单元测试-引用JSON读取函数;执行 :
# 导包 unittest、三角形、txt读取
import unittest
from UT.Day02.Code.sjx import Sjx
from UT.Day02.ReadData.read_txt import Read_Txt
# 实例化三角形
sjxClass=Sjx()
# 实例化txt
readTxtClass=Read_Txt()
class Test_Txt(unittest.TestCase):
    # 测试三角形程序
    def test001(self):
        for i in range(len(readTxtClass.readTxt())):
            # 调用三角形方法
            result=sjxClass.sjx(int(readTxtClass.readTxt()[i][0]),
                                           int(readTxtClass.readTxt()[i][1]),
                                           int(readTxtClass.readTxt()[i][2]))
            # 调用断言,判断三角形程序执行后的结果是否与预期结果相符
            self.assertEqual(result,readTxtClass.readTxt()[i][3])
            # 为了查看方便,我把执行成功后的数据打印一下
            print(readTxtClass.readTxt()[i][0], readTxtClass.readTxt()[i][1],                     readTxtClass.readTxt()[i][2], readTxtClass.readTxt()[i][3],"-->通过!")
if __name__ == '__main__':
    unittest.main()

难点分析:导包:不需要 读取方法:readlines()
1. 如何读取txt文本?
    with open(r'../DataXML/三角形.txt','r',encoding='utf-8') as f:
2. 如何去除行尾/n换行符?
        line.strip()


单元测试总结

--------------------------------------------------------------------------------

HTML报告生成

如何生成HTML报告?导入HTML报告模板模板:HTMLTestRunner.py

编写生成HTML模块
    # 导入unittest包
    import unittest
    # 导入 HTMLTestRunner模板包
    from UnitTest.Day02.ReadData.HTMLTestRunner import HTMLTestRunner
    #导入时间包
    import time
    # 定义测试模块路径
    dirpath='.'
    discorver=unittest.defaultTestLoader.discover(dirpath,pattern='test*.py')
    if __name__=='__main__':
        #存放报告的文件夹
        report_dir='../TestReport'
        #报告名称含时间,时间格式
        now=time.strftime("%Y-%m-%d %H_%M_%S")
        #报告完整路径+名称
        report_name=report_dir+'/'+now+'result.html'
        #打开报告写入结果
        with open(report_name,'wb')as f:
            runner=HTMLTestRunner(stream=f,title="UnitTest Report-SJX",description='Test Case Report')
            runner.run(disconver)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342