軟件測試教程 性能測試Loadrunner篇(二)

軟件測試教程 性能測試Loadrunner篇(二)


上節課主要講述了性能測試的基本知識,現在開始介紹性能測試工具Loadrunner。

Loadrunner是業界公認的權威性能測試工具,被譽為工業級的性能測試工具,支持廣泛的協議和平臺。

本節主要介紹以下內容:

Loadrunner安裝

Loadrunner的基本概念

開發測試腳本

Loadrunner安裝

現在以Loadrunner12為例,說明一下安裝過程。

1、雙擊HP LoadRunner 12.53 Community Edition.exe啟動安裝程序

2、安裝程序開始解壓,選擇默認路徑即可,點擊install

3、Loadrunner程序會使用到Visual C++的庫,如果缺少這些庫,安裝程序會提示安裝,這時選擇“確定”進行安裝即可,過程中如果提示重啟,按要求重啟即可,重啟后會繼續進行安裝過程。

4、按照默認操作一步步進行安裝

5、安裝過程中會有如下的提示。若無指定代理使用的證書,則去掉勾選。

image.png

6、至此,Loadrunner已安裝完畢。HP network Virtualization為可選項,可不安裝。

7、雙擊HP LoadRunner 12.53 Community Edition - Language Packs,安裝中文包。

系統將抽取語言包安裝包,可選擇抽取的語言包臨時存放路徑。建議直接默認即可,點擊【Install】

8、抽取安裝包完成后將自動關閉窗口(注此處只是把安裝包抽取出來了,要到抽取的安裝包中進行安裝),此時需要到上一步中選擇的路徑中找到語言安裝包。如未修改路徑則在以下路徑”C:\Temp\HP LoadRunner 12.02 Community Edition\DVD“打開該文件夾。點擊”Setup“

9、將自動打開安裝目錄,點擊【語言】

10、打開選擇語言文件夾,選擇要安裝的語言。本處依次打開如下文件【Chinese-Simplified】→【LoadRunner】→【LR_CHS】,點擊【LR_CHS】將進行安裝。(其實可以省略掉第8,9步,直接找到該文件安裝即可)

Loadrunner的基本概念

在使用loadrunner之前,先了解一下幾個概念:

  • Scenario:場景。所謂場景,是指在每一個測試過程中發生的事件,場景的設計需要
  • Vusers:虛擬用戶。LoadRunner使用多線程或多進程來模擬用戶對應用程序操作時產生的壓力。一個場景可能包括多個虛擬用戶,甚至成千上萬個虛擬用戶。
  • Vuser Script:腳本。用腳本來描述Vuser在場景中執行的動作。
  • Transactions:事務。事務代表了用戶的某個業務過程,需要衡量這些業務過程的性能。
  • rendezvous :集合。當我們測試多個用戶并發時,每個用戶執行到該事務腳本的先后順序是不確定的,所以得到的測試結果也并不是一個完全 并發的極限測試結果。在開始事務之前 ,插入一個“集合點”,那么在多用戶執行時,就可以將用戶請求停下來,直到用戶數量達到滿足的條件(默認是100%的用戶都到達集合點)。那么,所有的用戶都將同時發出接下來的請求。

Loadrunner的性能測試用例執行過程:

st=>start: 制定性能測試計劃
op=>operation: 開發測試腳本
op1=>operation: 設計測試場景
op2=>operation: 執行、監控測試場景
op3=>operation: 分析測試結果
op4=>operation: 對系統進行優化
op5=>operation: 優化后的測試
e=>end:yhua 

st->op->op1(right)->op2(right)->op3->op4->op5(right)->op2

制定性能測試計劃

這部分內容已經在上一節進行了講述。主要實現以下內容:

分析應用程序、確定測試目標、計劃怎樣執行

開發測試腳本

LoadRunner 使用虛擬用戶的活動來模擬真實用戶來操作Web 應用程序,而虛擬用戶的活動就包含在測試腳本中,所以說測試腳本對于測試來說是非常重要的。

開發測試腳本要使用 VuGen 組件。測試腳本要完成的內容有:

  • 每一個虛擬用戶的活動
  • 參數化
  • 定義事物
  • 定義檢查點

設計運行場景

運行場景描述在測試活動中發生的各種事件。一個運行場景包括一個運行虛擬用戶活動的Load Generator 機器列表,一個測試腳本的列表以及大量的虛擬用戶和虛擬用戶組。

運行、監視測試

一切配置妥當,開始運行測試。在運行過程中,需要監視各個服務器的運行情況(DataBase Server、Web Server 等)。

分析測試結果

所有前面的準備都是為了這一步。我們需要分析大量的圖表,生成各種不同的報告,最后會得出結論。

LoadRunner用3個主要功能模塊來覆蓋性能測試的基本流程。

  • Virtual User Generator

  • Controller

  • Analysis

其中Virtual User Generator使用在創建VU腳本階段,Controller用在定義場景階段和運
行場景階段,Analysis用在分析結果階段。

開發測試腳本

下面以Loadrunner安裝時附帶的樣例程序Web Tours進行講解。

C:\Program Files (x86)\HP\LoadRunner\WebTours,選擇StartServer.bat啟動服務。

錄制基本的用戶腳本

1、啟動 Visual User Generator 后,選擇新建腳本,因為要測試的是web項目,所以選擇協議為Web-HTTP/HTML,點擊創建后,進入主窗體

image.png

2、在解決方案資源管理器中可以看到該腳本的組成部分。簡單說明一下:VuGen 中的腳本分為三部分:vuser_init、vuser_end 和Action。vuser_init用于用戶初始化,vuser_end用于用戶清理工作。Action用于具體的需要測試的操作。類似于unittest等測試框架。

舉例說明:
一個測試場景為:用戶登錄系統,進行搜索操作,再進行退出系統。
這里,一般將登陸放置到vuser_init,退出放置到vuser_end,搜索放置到Action。

注意:在重復執行測試腳本時,vuser_init 和vuser_end 中的內容只會執行一次,重復執行的只是Action 中的部分。

3、選擇錄制操作,可以開始一次錄制操作,在錄制中需要填寫URL,這里以http://127.0.0.1:1080/WebTours/為例。錄制到操作說明是將腳本放置到哪里。在錄制中也可以修改腳本放置的地方。

開始錄制中,“立即”默認情況下是選中的,說明應用程序一旦啟動,VuGen 就會開始錄制腳本;如果沒有選中,應用程序啟動后,VuGen 出現對話框,待確認后才開始錄制。一般默認即可。

image.png

4、在以上頁面上有錄制選項可以進行一些高級選項的設置,這里一般不需要改動。

image.png

Recording 標簽頁:

  • 基于HTML的腳本:這種方式錄制的代碼只生成了一個函數,所有請求全放在這個函數里面,即:一個操作(可能包含多個請求)會生成一個函數,這種方式看起來比較簡潔

    基于HTML的腳本模式下-->高級選項兩種方式的區別:

    ? 描述用戶操作的腳本(e.g. web_link,web_submit_form):上下文相關的,依賴上下文才能提交,比較符合人們的操作習慣。

    ? 僅包含明確URL的腳本(e.g. web_url, web_submit_data):上下文不相關,每個函數都指定了具體的url地址,可以直接提交成功,如果只關注協議,不需要關注頁面,可使用這種方式錄制。

  • 基于URL的腳本:這種方式會生成很多函數,它將每個請求都單獨成一個函數,這種方式更接近請求-響應的本質

選擇哪種方式錄制,有以下參考原則:

  • 基于瀏覽器的應用程序推薦使用HTML-based Script
  • 不是基于瀏覽器的應用程序推薦使用 URL-based Script
  • 如果基于瀏覽器的應用程序中包含了 JavaScript 并且該腳本向服務器產生了請求,比如DataGrid 的分頁按鈕等,也要使用URL-based 方式錄制
  • 基于瀏覽器的應用程序中使用了 HTTPS 安全協議,使用URL-based 方式錄制

5、點擊開始錄制,loadrunner會自動調用IE,并開始錄制操作。這里以注冊為例進行錄制,錄制完畢后,點擊停止,錄制停止,返回到腳本界面,可以看到已錄制的腳本。

錄制過程中,在屏幕上會有一個工具條出現。錄制提供了暫停、停止、新增操作,增加事務、增加集合點等操作。

image.png

插入事務

當錄制完一個基本的用戶腳本后,在正式使用前我們還需要完善測試腳本,增強腳本的靈活性。一般情況下,我們通過以下方法來完善測試腳本。

事務(Transaction):為了衡量服務器的性能,我們需要定義事務。比如:我們在腳本中有一個數據查詢操作,為了衡量服務器執行查詢操作的性能,我們把這個操作定義為一個事務,這樣在運行測試腳本時,LoadRunner 運行到該事務的開始點時,LoadRunner 就會開始計時,直到運行到該事務的結束點,計時結束。這個事務的運行時間在結果中會有反映。插入事務操作可以在錄制過程中進行,也可以在錄制結束后進行。LoadRunner 可以在腳本中插入不限數量的事務。

通過菜單設計---在腳本中插入---開始事務、結束事務來進行事務的添加。

事務的狀態默認情況下是 LR_AUTO。一般情況下,我們也不需要修改,除非在手工編寫代碼時,有可能需要手動設置事務的狀態。可以通過步驟導航器來查看步驟的參數選項。

以上述注冊為例,在實際生成的腳本中含有打開首頁、注冊、退出登錄等多項操作。而我們實際需要關注的是注冊這一個事務的性能,那么就需要在注冊前后來加入事務。

lr_start_transaction("register");

    web_submit_form("login.pl_2", 
        "Snapshot=t8.inf", 
        ITEMDATA, 
        "Name=username", "Value=test", ENDITEM, 
        "Name=password", "Value=123456", ENDITEM, 
        "Name=passwordConfirm", "Value=123456", ENDITEM, 
        "Name=firstName", "Value=", ENDITEM, 
        "Name=lastName", "Value=", ENDITEM, 
        "Name=address1", "Value=", ENDITEM, 
        "Name=address2", "Value=", ENDITEM, 
        "Name=register.x", "Value=59", ENDITEM, 
        "Name=register.y", "Value=10", ENDITEM, 
        LAST);

    web_image("button_next.gif", 
        "Src=/WebTours/images/button_next.gif", 
        "Snapshot=t9.inf", 
        LAST);
lr_end_transaction("register", LR_AUTO);

插入集合點

插入集合點是為了衡量在加重負載的情況下服務器的性能情況。在測試計劃中,可能會要求系統能夠承受1000 人同時提交數據,在LoadRunner 中可以通過在提交數據操作前面加入集合點,這樣當虛擬用戶運行到提交數據的集合點時,LoadRunner 就會檢查同時有多少用戶運行到集合點,如果不到1000 人,LoadRunner 就會命令已經到集合點的用戶在此等待,當在集合點等待的用戶達到1000 人時,LoadRunner 命令1000 人同時去提交數據,從而達到測試計劃中的需求。

注意:集合點經常和事務結合起來使用。集合點只能插入到Action 部分,vuser_init和vuser_end 中不能插入集合點。

具體的操作方法如下:在需要插入集合點的前面,通過菜單操作:菜單設計---在腳本中插入---集合

lr_rendezvous("index");

參數化輸入

如果用戶在錄制腳本過程中,填寫提交了一些數據,比如要增加數據庫記錄。這些操作都被記錄到了腳本中。當多個虛擬用戶運行腳本時,都會提交相同的記錄,這樣不符合實際的運行情況,而且有可能引起沖突。為了更加真實的模擬實際環境,需要各種各樣的輸入。

參數化輸入是一種不錯的方法。

用參數表示用戶的腳本有兩個優點:

① 可以使腳本的長度變短。

② 可以使用不同的數值來測試你的腳本。例如,如果你企圖搜索不同名稱的圖書,你僅僅需要寫提交函數一次。在回放的過程中,你可以使用不同的參數值,而不只搜索一個特定名稱的值。

參數化包含以下兩項任務:

① 在腳本中用參數取代常量值。

② 設置參數的屬性以及數據源。

參數化僅可以用于一個函數中的參量。你不能用參數表示非函數參數的字符串。

例如在上面的注冊的例子中,我們已經注冊了test用戶,那么再次注冊就會失敗。也就是說Loadrunner腳本再次運行就會失敗。找到以下的代碼塊,在username中選中“test”字符串點擊右鍵選擇“使用參數替換”,就可以進行參數設置。

web_submit_form("login.pl_2", 
        "Snapshot=t8.inf", 
        ITEMDATA, 
        "Name=username", "Value=test", ENDITEM, 
        "Name=password", "Value=123456", ENDITEM, 
        "Name=passwordConfirm", "Value=123456", ENDITEM, 
        "Name=firstName", "Value=", ENDITEM, 
        "Name=lastName", "Value=", ENDITEM, 
        "Name=address1", "Value=", ENDITEM, 
        "Name=address2", "Value=", ENDITEM, 
        "Name=register.x", "Value=59", ENDITEM, 
        "Name=register.y", "Value=10", ENDITEM, 
        LAST);
image.png

下面介紹一下參數的類型。

日期/時間:很簡單,在需要輸入日期/時間的地方,可以用DateTime 類型來替代。其屬性設置也很簡單,選擇一種格式即可。當然也可以定制格式。

組名:在實際運行中,LoadRunner使用該虛擬用戶所在的Vuser Group 來代替。但是在VuGen 中運行時,Group Name將會是None

Load Generator Name:在實際運行中,LoadRunner 使用該虛擬用戶所在LoadGenerator 的機器名來代替。

迭代編號:在實際運行中,LoadRunner 使用該測試腳本當前循環的次數來代替。

隨機數字:隨機數。很簡單。在屬性設置中可以設置產生隨機數的范圍

唯一編號:唯一的數。在屬性設置中可以設置第一個數以及遞增的數的大小(每個Vuser的塊大小)。

注意:使用該參數類型必須注意可以接受的最大數。例如:某個文本框能接受的最大數為99。當使用該參數類型時,設置第一個數為1,遞增的數為1,但100 個虛擬用戶同時運行時,第100 個虛擬用戶輸入的將是100,這樣腳本運行將會出錯。

注意:這里說的遞增意思是各個用戶取第一個值的遞增數,每個用戶相鄰的兩次循環之間的差值為1。舉例說明:假如起始數為1,遞增為5,那么第一個用戶第一次循環取值1,第二次循環取值2;第二個用戶第一次循環取值為6,第二次為7;依次類推。

Vuser ID:設置比較簡單。在實際運行中,LoadRunner 使用該虛擬用戶的ID 來代替,該ID 是由Controller 來控制的。但是在VuGen 中運行時,Vuser ID 將會是–1。

File:需要在屬性設置中編輯文件,添加內容

用戶定義的函數:從用戶開發的dll 文件提取數據。

在上面的例子中,我們取隨機數就可以了。選擇隨機數后,點擊“屬性… ..”按鈕,進行屬性設置窗口

image.png

添入隨機數的取值范圍為(1-50),選擇一種數據格式。在更新值的時間中有以下幾個選項:

  • Each Occurrence:在運行時,每遇到一次該參數,便會取一個新的值
  • Each iteration:運行時,在每一次循環中都取相同的值
  • Once:運行時,在每次循環中,該參數只取一次值

這里我們用的是隨機數,選擇默認的Each Occurrence 就非常合適。

File等類型中有以下參數:
“選擇下一行 ”有以下幾種選擇:
Sequential:按照順序一行行的讀取。每一個虛擬用戶都會按照相同的順序讀取
Random:在每次循環里隨機的讀取一個,但是在循環中一直保持不變
Unique :唯一的數。注意:使用該類型必須注意數據表有足夠多的數。比如Controller 中設定20 個虛擬用戶進行5 次循環,那么編號為1 的虛擬用戶取前5個數,編號為2 的虛擬用戶取6-10 的數,依次類推,這樣數據表中至少要有100個數據,否則Controller 運行過程中會返回一個錯誤。

為了避免不正確的參數導致不可用,在設置后,可以選擇“模擬參數”來試運行

插入函數

VuGen 中可以使用C 語言中比較標準的函數和數據類型,語法和C 語言相同。下面簡單介紹一下比較常用的函數和數據類型。

在腳本頁面,通過右鍵-插入-新建步驟可以查看函數列表

  1. 控制腳本流程
if { } else { }
for{ }
while{ }

……………
總之 C 語言的控制流程的語句這里都可以直接使用

  1. 字符串函數

由于在 VuGen 腳本中使用最多的還是字符串,所以字符串函數在腳本中使用非常頻繁。具體的語法請參考幫助說明。

strcmp 比較兩個字符串
strcat 連接兩個字符串
strcpy 拷貝字符串

……………..
注意:在VuGen 中,以char聲明的字符串是只讀的,如果試圖給char類型的字符串賦值的話,編譯會通過,但在運行時會產生“Access Violation”的錯誤。解決這類問題,就是把字符串聲明為字符數組,比如char[100]。

  1. 輸出函數

輸出函數在調試腳本時非常有用。

lr_output_message 輸出一條消息
  1. LoadRunner 提供的標準函數
lr_eval_string 該函數功能是得到參數(參數化輸入中)當前的值
exg: lr_output_message("temp = %s", lr_eval_string("{WCSParam2}"));
lr_save_string 該函數功能是把一個字符串保存到參數中
exg: lr_save_string("439","WCSParam3");
web_reg_save_param("BODY",
        "LB=\"MESSAGE\":{\"",
        "RB=\":",
        LAST);


if(strcmp(lr_eval_string("{BODY}"),"msginfo")==0)
{
    lr_end_transaction("柜員登陸",LR_PASS);
     }
    else
     {
        lr_output_message("BODY=[%s]",lr_eval_string("{BODY}"));
         lr_end_transaction("柜員登陸",LR_FAIL);          

插入檢查點

在進行壓力測試時,為了檢查Web 服務器返回的網頁是否正確,VuGen 允許我們插入Text 檢查點,這些檢查點驗證網頁上是否存在指定的Text,還可以測試在比較大的壓力測試環境中,被測的網站功能是否保持正確。檢查點的含義和unittest中的斷言功能基本上一致。

通過菜單---查看---快照,可以查看到http數據視圖,選擇檢查的文本,選擇添加文本檢查步驟,即可添加一個檢查點。

web_reg_find("Text=Thank you, <b>test</b>, for registering and welcome to the Web Tours family.",LAST);
image.png

此時選擇“回放”,可以看到錯誤提示:

image.png

這是因為username我們已經做了參數化,在注冊成功后的歡迎頁面,已經不是test了。我們做如下改造:

web_reg_find("Text=Thank you, <b>{username}</b>, for registering and welcome to the Web Tours family.",LAST);
再回放一下試試,失敗還是成功?為什么?想一想參數中有什么需要修改的?

關聯

很多時候,一個項目的請求所以帶的參數會來源于前面請求返回的結果,而我們錄制的內容,則只是完整地記錄當時的請求參數,這通常不是我們想要的。

例如上面的例子中的檢查點文本中的test。就是根據輸出來顯示的,我們可以獲取該值來供后續使用。

在上圖中,選擇test,通過創建關聯,可以創建一個關聯,創建完畢后如下圖:

web_reg_save_param_ex(
        "ParamName=CorrelationParameter",
        "LB=you, <b>",
        "RB=<",
        SEARCH_FILTERS,
        "Scope=Body",
        LAST);  
//添加輸出,進行驗證 
lr_output_message("BODY=[%s]", lr_eval_string("{CorrelationParameter}"));

運行時設置

當完善了測試腳本后,需要對VuGen 的“運行時設置” 進行配置。

在“解決方案資源管理器”視圖中選擇“運行時設置”,常用的設置內容如下:

1、常規-其他-錯誤處理:

一般不需要改動,但是在Controller運行時,可以設置“出現錯誤時仍繼續”,來統計錯誤率

2、常規-其他-自動事務

當我們手工設置了事務時,建議取消該項,以免Controller運行時事務太多

3、常規-運行邏輯-迭代數

根據需要變動,一般在調試腳本時可以設置為多次迭代

4、常規-日志

在調試階段可勾選“啟動日志記錄”,腳本穩定時可取消該項

5、常規-思考時間

忽略時會對服務器造成更大的壓力,而增加思考時間可以更好的模擬用戶使用

6、internet協議-首選項-啟用圖像或文本檢查

不勾選“啟用圖像或文本檢查”,web_find和web_image_check設置的檢查點在運行時無效

7、工具-選項-腳本-回放

如果勾選“回放期間顯示運行時查看器”,則在運行時會啟動瀏覽器

單機運行測試腳本

經過以上的各個步驟后,腳本就可以運行了。運行腳本可以通過菜單或者工具欄來操作。

執行“運行”命令后,VuGen 先編譯腳本,檢查是否有語法等錯誤。如果有錯誤,VuGen將會提示錯誤。雙擊錯誤提示,VuGen 能夠定位到出現錯誤的那一行。為了驗證腳本的正確性,我們還可以調試腳本,比如在腳本中加斷點等。

如果編譯通過,就會開始運行。然后會出現運行結果。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376