构建自动化测试框架

使用不同的测试工具,包括单元测试工具、功能测试工具和性能测试工具等,但它们之间缺乏很好的联系、相互之间比较孤立,没有形成完整的体系或没有形成我们心目中一直渴望获得的自动化测试流水线。自动化测试不等同于“简单地使用几个测试工具”,而是能构成一个全过程的自动化测试,将代码构建、软件包快速验证、部署、测试执行到报告生成等各个环节紧密联系起来,形成一个自动化体系。

为何要建立自动化测试框架

最初的测试工具只提供了简单的捕捉/回放功能,记录键盘和鼠标的操作,并捕捉屏幕变化,然后播放所记录的操作,进行验证。这样的脚本很难维护,从而要求开发功能和灵活性更强的测试工具,并能将这些工具很好地整合起来,使整个自动化测试过程的各个部分或各个阶段能很好地衔接在一起。所以,才有了前面所介绍的Selenium系列工具,将脚本录制、开发、执行等集成在一起,向我们展示自动化测试框架的魅力。

为何要建立自动化测试框架?

1) 要构成一个完整的自动化体系,真正形成自动化测试的流水线,使整个测试过程能一气呵成。这其中,不仅包括自动化测试的执行,还包括自动化测试脚本的开发、软件包自动部署及测试报告自动生成等,将自动化开发、执行和日常工作融合在一起,使测试人员轻松工作,并显著提高测试工作效率。

2) 软件需求变化频繁或软件迭代开发方法不断推陈出新,这些都给软件自动化测试带来极大的挑战。软件需求发生变化,会引起一系列的变化,从设计到代码,包括软件的界面、操作逻辑等都发生了改变,原来写的自动化测试脚本在软件的新版本上运行不起来,会遇到各种问题,必须对脚本进行维护。如果脚本维护的工作量很大,必然严重地影响软件自动化测试的投入产出比。例如就测试自动化的脚本来看,就经历了几个典型的发展阶段,从模块化驱动测试、数据驱动测试,再到关键字驱动测试、基于模型测试和混合模式测试等,这其中一个主要原因也是为了降低脚本维护的工作量。

3) 因为各个层次测试须要共享某些测试数据,各个阶段的测试往往要前后衔接起来,前一阶段的测试输出有可能成为下一阶段的测试输入,更重要的是如何更好地支持数据驱动测试,从而更充分地覆盖各种测试。人们还希望能将不同测试的结果综合起来,进行分析和评估。

4) 由于软件规模越来越大,软件的开发团队人数多,可能分布在不同城市、不同地区甚至不同国家,团队之间需要协作。而且软件开发还会占有较多的硬件资源,特别是性能测试、可靠性测试有时需要几百台机器参与测试。如果不能充分利用这些资源,可能会给企业造成较大的成本浪费。但是,如果我们能事先灵活地安排任务,按照预定的计划运行测试,就能对测试资源进行灵活的调度,在保证各项任务及时执行的情况下,充分利用资源。

恕我渣渣乱说话:个人认为为何要建立自动化测试框架?原因有三:1) 框架移植性好,一个项目或者同类项目间可以借用。2) 全过程的自动化测试,将各个阶段的测试管理起来,便于做计划安排任务,便于跟踪暴露的问题,便于需求端到端跟踪和交付。3) 框架便于维护,比如测试脚本的分层便于更好的维护,比如测试数据可以在各个测试阶段得到复用。

什么是自动化测试框架

为了全面地满足自动化测试的需要,就要建立合适的自动化测试框架。为了构造有效的自动化测试框架,首先要了解自动化测试对测试框架构建的需求,然后基于这个需求,并借鉴软件开发框架的思想,来设计自动化测试框架。

一、须要解决哪些问题

自动化测试框架,最根本的是要构造一个良好的工作空间,使其能够容纳各种类型的测试工具。这些工具要能够相互兼容、共享测试数据。如果能够即插即用,任何测试工具都可以动态、方便地加入系统或从系统中移除,那就更好。

其次,能够监控测试执行的过程,包括监控测试资源(如CPU、内存等)的使用,及时收集来自于不同测试工具的测试结果,并将这些结果进行归类和分析,生成相应的测试报告,通过邮件和网站等发布出去。

再者,为了让测试能够在某个特定时刻(例如晚上)自动执行,自动化测试框架应具有事先预定(schedule)任务能力,并能够支持维护测试环境和管理测试资源。包括管理硬件资源列表、支持测试状态查询和自动分发测试任务到相应的测试机器上,并在规定的时间内完成测试任务。

最后,自动化测试框架还要能支持自动化测试的前期任务,支持测试脚本的录制、编辑和调试;支持测试脚本的快速开发和良好的维护性,而且能够支持测试用例、测试套件(Test suite)和其他测试活动的管理

要构建良好的自动化测试框架,还要提供一些基础设施支持自动化测试,例如邮件服务、跨平台的通信服务、分布式开发和运行环境等,如下图。

自动化测试框架要解决的问题

一个良好的自动化测试框架具有下列能力

1) 提供非常有效的测试工作流程模型,如任务安排、执行、通知结果和生成报告等完整的过程支持;

2) 支持多种脚本语言的录制、编辑、调试和回放等集成开发环境;

3) 完成各类测试任务的执行

4) 有良好的扩充能力,如和第三方插件、工具的集成;

5) 支持数据驱动、关键字驱动等脚本模式

6) 支持分布式处理、远程调用等不同的运行方式

7) 获取测试覆盖率。

如果进行进一步抽象,自动化测试框架可以理解为就是为了解决自动化测试中的公共问题而搭建的,这些问题包括公用的对象、方法、环境或数据,这些可以看作自动化测试框架的要素。

1) 公用的对象不同的测试用例会有一些相同的对象(如窗口、按钮、菜单等)被重复使用,而这些公用的对象可以被抽取出来,在编写脚本时随时调用。如果需求发生变化,只要修改这些公用对象属性即可,而不须修改太多的测试脚本。这就要求我们构建对象库,并建立实体对象和逻辑对象之间的映射。

2) 公用的方法。公用的方法比较多,相当于脚本的基础函数,可以构成基础函数库,供上层脚本调用

3) 公用的环境或数据。许多测试用例会在相同的测试环境上运行或使用相同的测试数据,可将不同的测试环境或数据封装起来,和测试用例进行灵活的组合,增强脚本执行的灵活性,并覆盖更广的测试范围,降低测试风险。

二、软件开发框架的启发

框架(Framework)是为开发者定制的应用骨架或应用的体系结构,着重从系统的可重用性进行设计,确定系统各个部分的作用、分工及其依赖关系、上下文(Context)关系或控制流程。应用框架更注重于面向专业领域的软件重用,提供这一应用领域所需要的基础服务,以缩短大型应用软件系统的开发周期,提高开发质量。应用框架还强调系统的可扩充性,使系统具有很好的适应性,很容易将一些功能强大的第三方组件集成到系统中来,共享基础设施和基础服务。

Java软件开发中有很多框架,例如Struts、Spring和Hibernate等都遵循模型-视图-控制(MVC)设计模式,将商业逻辑和描述分开,由一个逻辑流控制器来协调来自客户端的请求和服务器所提供的各种服务。这些框架提供了实现不同功能的技术和应用接口,而具体的实现则由开发人员来完成,一些特定的参数、表现可以通过XML配制文件、java属性文件等辅助完成。Struts采用了Servlet、JSP和自定义的标记库等技术,构造了一个简单的、清晰MVC框架,从而极大地加快了开发的速度,并能适应需求的变化,改善软件系统的可维护性。

J2EE的Struts框架的架构示意图

软件自动化测试框架,虽然不完全相同于软件开发框架,但也有很大的相似性。因为自动化测试也包含了大量的(脚本)开发工作,包括脚本调试和版本控制管理等。自动化测试也应该将业务逻辑具体操作分开,从而形成关键字驱动脚本技术。在构造自动化测试框架时候,底层需要一个良好的通信机制,使各个组件之间实现消息传递,并提供标准的接口,从而使自动化框架具有良好的兼容性,容易将第三方工具集成进来。

对不同的应用领域或不同的测试水平,自动化测试的框架有不同的构成,但我们必须清楚如何满足自动化测试的基本要求,了解自动化测试框架的基本构成,从而在此基础上,根据自己组织的实际情况进行扩充,构造适应性更强、效率更高、功能更强的自动化测试框架。

为了能开展自动化测试的工作,首先需要基础设施(infrastructure)来支撑测试工具的运行,包括网络、邮件服务器、FTP服务器等。其次是执行自动化测试,形成一套机制来保证测试脚本的执行。具体地说就是,先建立测试环境,创建和执行测试套件,然后获取执行状态并给出测试结果报告。根据这个分析,描述一个自动化测试框架的雏形。

自动化测试框架的雏形

这个雏形给出了自动化测试框架的基本构成要素,包括执行器、脚本管理器、报告生成器等,也较清楚地描述了这些要素之间的关系。但该框架并没给出其他一些必要信息:

1) 测试脚本的层次性,如何分离业务逻辑和系统基本操作?

2) 自动化测试工具的接口。

3) 被测试系统的对象映射。

4) 如何事先安排测试任务?

5) 如何管理有效测试资源?

如果要在测试脚本上分离业务逻辑和系统基本操作,必须将各种基本操作封装为特定的关键字每个关键字对应一个函数,而业务逻辑可以由关键字加参数来描述,如下图。

关键字驱动脚本框架示意图

事先安排完任务,还须要开发具有相应触发机制的引擎(engine/ cron job)来执行测试任务。它负责完成自动部署软件包、向各台远程测试机器分发脚本并启动测试工具等工作。

几个典型的自动化测试框架

1. JUnit单元测试

JUnit是一个单元测试工具,同时它也具有一个自动化测试框架的许多特征,它提供一个单元测试的基本思想,或者说解决方案。JUnit作为优秀的单元测试框架,被广泛借鉴和采纳,产生了许多类似的单元测试框架,如PhpUnit、HTMLUnit等。

2. Selenium 功能测试web

自动化测试框架首先要提供脚本开发的集成环境(IDE)。这和一般程序开发环境没什么不同,提供编写代码、调试、代码管理、编译、预运行等一系列功能,包括一个编辑器、调试器、浏览器等组件。但是,自动化测试脚本开发所不同的是:

1) 能针对被测试的应用系统来运行和调试脚本;

2) 能够录制手工测试或操作的行为和过程;

3) 支持数据驱动脚本、关键字驱动脚本的特殊需要。

Selenium有一个IDE,可以录制对Web页面操作的过程,自动生成线性的脚本。Selenium IDE还可以对脚本进行编辑,包括插入新命令行或注释行、设置断点或起始位置、单步调试等,而且提供测试用例(test case)和测试套件(test suite)创建、关键字提示等功能。

解决了IDE还不够,测试用例和套件的管理、测试结果分析和显示等都是自动化测试框架的重要组成部分。Selenium也提供了一个运行的环境(TestRunner),分为4个区域:测试套件(Test suite)、当前被执行的测试(Current Test)、执行操作区域(Control Panel,控制面板)和浏览器实际运行动态显示区域。

执行整个测试套件或当前的测试用例,单步执行测试用例。运行后,就可以获得测试结果:显示运行的一个测试失败,其中3个验证点通过、1个验证点没通过和1个验证点没执行完。

从上面的介绍可以看出,一个基本的自动化测试框架包括了下列功能:

1) 测试脚本开发集成环境(IDE),包括脚本编辑、调试等;

2) 测试用例和测试套件的创建和管理;

3) 测试执行环境,包括执行状态监控和测试结果的显示等。

受到同源策略(Same Origin Policy)的影响,使得JavaScript脚本只允许阅读/修改来自相同源头的HTML,不能测试跨站点的页面,受限于基础站点(BaseURL)的页面。要解决这样的问题,可以设定代理服务器,通过代理服务器欺骗浏览器,使JavaScript看似来自相同的远程服务器,从而符合同源策略。这样就有了Selenium RC。

Selenium RC不仅可以解除同源策略的限制,而且可以支持Java、C#、Perl、PHP、Python、Ruby等语言编写的脚本,以适应不同用户的习惯和需求。Selenium RC具有更强的扩充能力和处理能力,它将自己的测试整合到一个持续集成的工具中,可以和Java或.Net的相应工具进行集成,如Ant、Maven等。

Selenium RC运行的机制

目前许多应用软件系统是分布式的,服务器之间或客户端之间须要相互协作,因此,要求测试脚本能处理这种协同关系。运行测试脚本的机器之间具有相互依赖性,如某台机器运行到某一步,须要等待另一台机器运行完一段脚本后,再继续向下执行脚本,这时要求测试工具之间可以相互通信。测试框架要支持一种分布式的通信机制,确保参与测试的客户端可以协同工作。为此, Selenium推出了服务于这一目的的Selenim Grid。

Selenium Grid能充分利用测试环境中的机器,允许并行地在不同的环境上运行多个测试任务(即Selenium RC的实例),而且可以在一台机器上控制它们,而不要关心实际的物理环境。这样,Selenium Grid可以大大节省测试执行的时间,极大地加快Web应用功能的测试。

Selenium Grid运行的机制
集成测试框架Fit

集成测试框架(Framework for Integrated Tests,FIT)是由Ward Cunningham (http://en.wikipedia.org/wiki/Ward_Cunningham)开发的、开放的通用框架,可以帮助需求定义人员与开发人员进行沟通,以实现自动化验收测试,进而保证完成足够的回归测试。FIT在自动化测试框架思想上,给我们的最大启示是如何将业务流程和计算机操作(测试动作)分离,以及如何用最简单的语言来描述需求和测试用例。FIT通过一张表格式的需求描述,很好地体现了关键字驱动脚本和数据驱动脚本的完美结合

FIT需求记录格式采用HTML表格和文档,而不是Java或C#代码,将需求通过表格模型来表示,充当测试所需的数据模型,以描述实际的数据输入和测试的预期输出。FIT的设计目的就是让用户或业务团队在开发周期中,尽早地与开发人员协作。创建应用程序需求的简单表格式模型,可以让每个人清楚地看出代码和需求是否是一致的。使用FIT,可以编写出自动运行的验收测试用例,以确认所开发的软件是否满足了用户的需求,可以作为持续构建过程的一部分来确保所构建出来的版本是正确的。

Fixture就是基于FIT的测试。它把HTML表格或Wiki页面格式的验收测试和应用程序的实际代码联系起来,测试那些代码并显示结果。要创建一个Fixture类型,必须扩展对应的FIT Fixture,将它映射到对应的表,不同类型的表映射到不同的业务场景。简单来讲,FIT能够读取HTML文件中的表格。针对每个表格,FIT通过Fixture来解释,即该Fixture会驱动“被测系统”来对表格中给出的测试用例进行检验

Fixture充当Fit表格和要测试系统间的媒介,起协调作用,完成表格中给出的测试。下表包含了操作关键字和输入数据,是 “数据驱动脚本”和“关键字驱动脚本”完美结合的典型实例。

自动化测试框架SAFS/STAF/STAX

这个分布式自动化测试框架支持远程调用、支持跨平台和多语言的自动化测试,由3部分组成:SAFS、STAF和STAX。

SAFS(Software Automation Framework  Support,http://safsdev.sourceforge.net/Default.htm)是基于数据驱动和关键字驱动的思想设计的开源自动化测试框架, 支持跨平台、多语言的应用。

STAF(Software Test Automation  Framework,http://staf.sourceforge.net/index.php)围组件重用的理念,通过服务调用(如远程处理、资源管理、监控等)来完成自动化架构的构造。STAF作为自动化测试框架,提供一种可插拔的机制,支持多平台与多语言的分布式结构。

STAX(STAF eXecution engine,http://staf.sourceforge.net/getstax.php)是基于STAF的执行引擎,它采用XML格式描述。在XML文件中可定义测试工作流,可以实现并行执行、嵌套测试用例、控制运行时间等,STAX支持Java和Python模块。

1. SAFS

SAFS框架由测试表、核心数据驱动引擎、成员函数库、支持库、应用映射表组成。

SAFS框架的构成和运行原理

1) 测试表(Test Tables):保存测试数据和关键字,分为高层测试表(CycleTables)、中层测试表(Suite Tables)和低层测试表(Application Map,特定的应用程序元素),下层的测试表可被上层的测试表所调用。

2) 核心数据驱动引擎(Core Data Driven  Engine):与测试表对应,分为高层驱动器(也叫循环驱动器)、中层驱动器(也叫组装驱动器)和低层驱动器(也叫步骤驱动器)。上层的驱动器读取相应测试表的关键字,并逐级传递给下层的驱动器,最后由低层驱动器调用关键字库中的指令对应的组件函数来执行。

3) 组件函数库(Component Function):实现了用户对界面对象的各种操作指令,它在被测应用和自动化工具之间提供了一个隔离层。

4) 支持库(Support Libraries):通用的程序和工具库,提供诸如数据库访问、字符串操作、文件访问、日志记录等基础性的支持功能。

5)  应用映射表(Application Map):对应用中的对象定义一套命名规范,将这些实际对象的名字和自动化工具识别的对象名联系起来,形成映射表,从而使应用对象元素和测试对象名分离,提高了脚本的可维护性。

2. STAF

STAF比较适应构造复杂的、分布式的测试环境。通过STAF将测试任务分发到不同的测试环境去执行,在大量不同的测试机上执行相应的测试脚本,并可以方便地收集测试结果。借助执行引擎STAX,测试人员只须要配置XML文件便可实现STAF任务管理,使STAF的应用变得更为简便。

使用STAF可快速构造自动化测试环境,STAF的服务调用系统也使大家创建自动测试用例、管理测试用例更加方便。STAF在功能级别实施服务调用,各个服务端点(称作STAF客户端)是对等的,从一个端点可直接调用另一端点提供的服务,如图所示。

STAF/STAX构成及其通信服务机制

1) 将环境需求最小化(包括硬件与软件);

2)  支持各种脚本语言的应用,包括Java、python、Perl及命令行shell环境等;

3) 易于扩展,用户根据需要可随时创建一个服务插入到STAF体系中。

STAF是一个小巧的后台程序,并基于服务(Services)来构建自动化框架,在STAF中使用的所有组件都是服务。STAF服务就是一系列功能的集合,作为可重用组件而分布在不同的远程机器上。STAF还提供轻量级分发机制,负责将请求转发给这些服务。

STAF服务分为内部(Internal)服务和外部(External)服务。内部服务被集成进STAFProc,提供一些关键性的功能。

3. STAX

STAX是构建于STAF之上的调度引擎,它可以控制STAF执行各种命令。我们可以将大量须要执行的STAF命令写成STAX脚本,STAX运行时会自动解析脚本并控制STAF执行这些命令。STAX脚本相当于STAF命令的批处理文件。STAX脚本使用XML格式描述,比较容易编写。STAX可以控制运行时间、嵌套测试用例,还支持并行测试。

理想的自动化测试框架

自动化测试框架应支持脚本录制、脚本开发、测试用例和测试套件的创建和执行、远程控制、分布式通信等功能。这样可以基本满足测试自动化的需要,但理想情况下,自动化测试框架还须要支持单元测试并与开发环境集成,如将开发IDE Eclipse、软件配置管理工具CVS/Subversion和软件包构建工具Ant/Maven等集成到框架中,以支持每日构建和自动验证测试。

如果自动化测试框架能管理测试项目、安排任务,将产品用户需求和测试需求很好地结合起来,那么测试目标更明确,测试的效率会得到进一步提高。测试结果的分析也是很重要的,一般也要求在自动化测试框架中得到解决。最重要的是容易使用,将各个工具集成起来,并能很好地使用这些工具。例如,openqa.org社区提供了一个工具——Bromine,它集成了Selenium Core/RC,可非常容易地跟踪和管理测试项目、需求、测试计划、测试用例和缺陷,可以提交缺陷和将缺陷分派给相应的开发人员,浏览和分析测试结果。

一个理想的自动化测试框架能解决上述问题,提供一个分布式的通信平台、友好的人机交互界面和开放式架构,将自动化测试中所需要的各个关键部分有机地集成起来,形成一个为自动化测试服务的、完整的、层次清楚的开发平台和运行环境

1) 综合管理平台,可以将自动化测试中所有的工作内容管理起来,相当于一个统一的入口(Portal),可以浏览每部分的内容。

2) 基于业务驱动的脚本集成开发环境,这样比较容易构造关键字驱动的脚本,为此要建立软件系统的对象库,并将这些对象映射为脚本中的逻辑对象,以减少软件需求变化对脚本的影响。这个集成开发环境还包括脚本录制、编辑等功能,并能和CVS、Ant等工具集成。其中库函数可以看作是关键字列表和关键字实现,而对象映射可以看作是对象库和映射关系构成的。

3) 安排(schedule)测试任务,使任务可以定时启动,自动执行测试任务。

4)  在测试过程中,能够监控测试资源,并及时发现问题,发出警告,并保留(记录)相关数据。

5)  控制中心(控制器),驱动测试工具,可以调用测试任务,并能将测试任务、测试脚本等分发给远程机器。

6) 远程机器执行测试任务,通过代理实现,而代理由控制中心来控制。


本文来自朱少民老师的《轻轻松松自动化测试》,如有侵权,请通知后删除

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