Python3.0 Django2.0.4 搭建項(xiàng)目(五)

接上一篇Python3.0 Django2.0.4 搭建項(xiàng)目(四)诗箍,這一篇我們將為它創(chuàng)建一些自動化測試癣籽。

自動化測試簡介

自動化測試是什么?

測試滤祖,是用來檢查代碼正確性的一些簡單的程序筷狼。

測試在不同的層次中都存在。有些測試只關(guān)注某個很小的細(xì)節(jié)(某個模型的某個方法的返回值是否滿足預(yù)期匠童?)埂材,而另一些測試可能檢查對某個軟件的一系列操作(某一用戶輸入序列是否造成了預(yù)期的結(jié)果?)汤求。其實(shí)這和我們在 教程第 2 部分俏险,里做的并沒有什么不同,我們使用 shell 來測試某一方法的功能扬绪,或者運(yùn)行某個應(yīng)用并輸入數(shù)據(jù)來檢查它的行為竖独。

真正不同的地方在于,自動化 測試是由某個系統(tǒng)幫你自動完成的挤牛。當(dāng)你創(chuàng)建好了一系列測試莹痢,每次修改應(yīng)用代碼后,就可以自動檢查出修改后的代碼是否還像你曾經(jīng)預(yù)期的那樣正常工作墓赴。你不需要花費(fèi)大量時(shí)間來進(jìn)行手動測試竞膳。

為什么需要寫測試

寫一些自動測試并不能讓它工作的更好。如果寫一個投票應(yīng)用是你想用 Django 完成的唯一工作竣蹦,那你確實(shí)沒必要學(xué)寫測試顶猜。但是如果你還想寫更復(fù)雜的項(xiàng)目,現(xiàn)在就是學(xué)習(xí)測試寫法的最好時(shí)機(jī)了痘括。

測試將節(jié)約你的時(shí)間

在某種程度上长窄,能夠「判斷出代碼是否正常工作」的測試,就稱得上是個令人滿意的了纲菌。在更復(fù)雜的應(yīng)用程序中挠日,組件之間可能會有數(shù)十個復(fù)雜的交互。

在更加復(fù)雜的應(yīng)用中翰舌,各種組件之間的交互可能會及其的復(fù)雜嚣潜。改變其中某一組件的行為,也有可能會造成意想不到的結(jié)果椅贱。判斷「代碼是否正常工作」意味著你需要用大量的數(shù)據(jù)來完整的測試全部代碼的功能懂算,以確保你的小修改沒有對應(yīng)用整體造成破壞——這太費(fèi)時(shí)間了只冻。

尤其是當(dāng)你發(fā)現(xiàn)自動化測試能在幾秒鐘之內(nèi)幫你完成這件事時(shí),就更會覺得手動測試實(shí)在是太浪費(fèi)時(shí)間了计技。當(dāng)某人寫出錯誤的代碼時(shí)喜德,自動化測試還能幫助你定位錯誤代碼的位置。

有時(shí)候你會覺得垮媒,和富有創(chuàng)造性和生產(chǎn)力的業(yè)務(wù)代碼比起來舍悯,編寫枯燥的測試代碼實(shí)在是太無聊了,特別是當(dāng)你知道你的代碼完全沒有問題的時(shí)候睡雇。

然而萌衬,編寫測試還是要比花費(fèi)幾個小時(shí)手動測試你的應(yīng)用,或者為了找到某個小錯誤而胡亂翻看代碼要有意義的多它抱。

測試不僅能發(fā)現(xiàn)錯誤秕豫,而且能預(yù)防錯誤

「測試是開發(fā)的對立面」,這種思想是不對的抗愁。

如果沒有測試馁蒂,整個應(yīng)用的行為意圖會變得更加的不清晰。甚至當(dāng)你在看自己寫的代碼時(shí)也是這樣蜘腌,有時(shí)候你需要仔細(xì)研讀一段代碼才能搞清楚它有什么用沫屡。

而測試的出現(xiàn)改變了這種情況。測試就好像是從內(nèi)部仔細(xì)檢查你的代碼撮珠,當(dāng)有些地方出錯時(shí)沮脖,這些地方將會變得很顯眼——就算你自己沒有意識到那里寫錯了。

測試使你的代碼更有吸引力

你也許遇到過這種情況:你編寫了一個絕贊的軟件芯急,但是其他開發(fā)者看都不看它一眼勺届,因?yàn)樗鄙贉y試。沒有測試的代碼不值得信任娶耍。 Django 最初開發(fā)者之一的 Jacob Kaplan-Moss 說過:“項(xiàng)目規(guī)劃時(shí)沒有包含測試是不科學(xué)的免姿。”

其他的開發(fā)者希望在正式使用你的代碼前看到它通過了測試榕酒,這是你需要寫測試的另一個重要原因胚膊。

測試有利于團(tuán)隊(duì)協(xié)作

前面的幾點(diǎn)都是從單人開發(fā)的角度來說的。復(fù)雜的應(yīng)用可能由團(tuán)隊(duì)維護(hù)想鹰。測試的存在保證了協(xié)作者不會不小心破壞了了你的代碼(也保證你不會不小心弄壞他們的)紊婉。如果你想作為一個 Django 程序員謀生的話,你必須擅長編寫測試辑舷!

開始寫我們的第一個測試

首先得有個 Bug

剛好我們的 polls 應(yīng)用現(xiàn)在就有一個小 bug 需要被修復(fù):我們的要求是如果 Question 是在一天之內(nèi)發(fā)布的喻犁, Question.was_published_recently() 方法將會返回 True ,然而現(xiàn)在這個方法在 Question 的 pub_date 字段比當(dāng)前時(shí)間還晚時(shí)也會返回 True(這是個 Bug)。

你能從管理頁面確認(rèn)這個 bug 真實(shí)存在肢础。在 shell 創(chuàng)建一個發(fā)布日期是未來某天的投票还栓,在投票列表里你會看到它被標(biāo)明為最近發(fā)布(published recently):

import datetime
from django.utils import timezone
from polls.models import Question
future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
future_question.was_published_recently()
True
image.png

因?yàn)閷戆l(fā)生的是肯定不是最近發(fā)生的,所以代碼明顯是錯誤的传轰。

創(chuàng)建一個測試來暴露這個 bug

我們剛剛在 shell 里做的測試也就是自動化測試應(yīng)該做的工作蝙云。所以我們來把它改寫成自動化的吧。

按照慣例路召,Django 應(yīng)用的測試應(yīng)該寫在應(yīng)用的 tests.py 文件里。測試系統(tǒng)會自動的在所有以 tests 開頭的文件里尋找并執(zhí)行測試代碼波材。

將下面的代碼寫入 polls 應(yīng)用里的 tests.py 文件內(nèi):

polls/tests.py


import datetime

from django.utils import timezone
from django.test import TestCase

from .models import Question


class QuestionModelTests(TestCase):

    def test_was_published_recently_with_future_question(self):
        """
        was_published_recently() returns False for questions whose pub_date
        is in the future.
        """
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)

我們創(chuàng)建了一個 django.test.TestCase 的子類股淡,并添加了一個方法,此方法創(chuàng)建一個 pub_date 時(shí)未來某天的 Question 實(shí)例廷区。然后檢查它的 was_published_recently() 方法的返回值——它 應(yīng)該 是 False唯灵。

運(yùn)行測試

在終端中,我們通過輸入以下代碼運(yùn)行測試:

python manage.py test polls

你將會看到運(yùn)行結(jié)果:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/mysite/polls/tests.py", line 18, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

以下是自動化測試的運(yùn)行過程:

1.python manage.py test polls 將會尋找 polls 應(yīng)用里的測試代碼
2.它找到了 django.test.TestCase 的一個子類
3.它創(chuàng)建一個特殊的數(shù)據(jù)庫供測試使用
4.它在類中尋找測試方法——以 test 開頭的方法隙轻。
5.在 test_was_published_recently_with_future_question 方法中埠帕,它創(chuàng)建了一個 pub_date     
    值為 30 天后的 Question 實(shí)例。
6.接著使用 assertls() 方法玖绿,發(fā)現(xiàn) was_published_recently() 返回了 True敛瓷,而我們期望它返 
     回 False。

測試系統(tǒng)通知我們哪些測試樣例失敗了斑匪,和造成測試失敗的代碼所在的行號呐籽。

修復(fù)這個 bug

我們早已知道,當(dāng) pub_date 為未來某天時(shí)蚀瘸, Question.was_published_recently() 應(yīng)該返回 False狡蝶。我們修改 models.py 里的方法,讓它只在日期是過去式的時(shí)候才返回 True:

polls/models.py


def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now

然后重新運(yùn)行測試:

image.png

發(fā)現(xiàn) bug 后贮勃,我們編寫了能夠暴露這個 bug 的自動化測試贪惹。在修復(fù) bug 之后,我們的代碼順利的通過了測試寂嘉。

將來奏瞬,我們的應(yīng)用可能會出現(xiàn)其他的問題,但是我們可以肯定的是垫释,一定不會再次出現(xiàn)這個 bug丝格,因?yàn)橹灰唵蔚倪\(yùn)行一遍測試,就會立刻收到警告棵譬。我們可以認(rèn)為應(yīng)用的這一小部分代碼永遠(yuǎn)是安全的显蝌。

更全面的測試

我們已經(jīng)搞定一小部分了,現(xiàn)在可以考慮全面的測試 was_published_recently() 這個方法以確定它的安全性,然后就可以把這個方法穩(wěn)定下來了曼尊。事實(shí)上酬诀,在修復(fù)一個 bug 時(shí)不小心引入另一個 bug 會是非常令人尷尬的。

我們在上次寫的類里再增加兩個測試骆撇,來更全面的測試這個方法:

polls/tests.py


def test_was_published_recently_with_old_question(self):
    """
    was_published_recently() returns False for questions whose pub_date
    is older than 1 day.
    """
    time = timezone.now() - datetime.timedelta(days=1, seconds=1)
    old_question = Question(pub_date=time)
    self.assertIs(old_question.was_published_recently(), False)

def test_was_published_recently_with_recent_question(self):
    """
    was_published_recently() returns True for questions whose pub_date
    is within the last day.
    """
    time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
    recent_question = Question(pub_date=time)
    self.assertIs(recent_question.was_published_recently(), True)

現(xiàn)在瞒御,我們有三個測試來確保 Question.was_published_recently() 方法對于過去,最近神郊,和將來的三種情況都返回正確的值肴裙。

再次申明,盡管 polls 現(xiàn)在是個非常簡單的應(yīng)用涌乳,但是無論它以后成長到多么復(fù)雜蜻懦,要和其他代碼進(jìn)行怎樣的交互,我們都能保證進(jìn)行過測試的那些方法的行為永遠(yuǎn)是符合預(yù)期的夕晓。

測試視圖

我們的投票應(yīng)用對所有問題都一視同仁:它將會發(fā)布所有的問題宛乃,也包括那些 pub_date 字段值是未來的問題。我們應(yīng)該改善這一點(diǎn)蒸辆。如果 pub_date 設(shè)置為未來某天征炼,這應(yīng)該被解釋為這個問題將在所填寫的時(shí)間點(diǎn)才被發(fā)布,而在之前是不可見的躬贡。

針對視圖的測試

為了修復(fù)上述 bug 谆奥,我們這次先編寫測試,然后再去改代碼逗宜。事實(shí)上雄右,這是一個簡單的「測試驅(qū)動」開發(fā)模式的實(shí)例,但其實(shí)這兩者的順序不太重要纺讲。

在我們的第一個測試中擂仍,我們關(guān)注代碼的內(nèi)部行為。我們通過模擬用戶使用瀏覽器訪問被測試的應(yīng)用來檢查代碼行為是否符合預(yù)期熬甚。

在我們動手之前逢渔,先看看需要用到的工具們。

Django 測試工具之 Client

Django 提供了一個供測試使用的 Client 來模擬用戶和視圖層代碼的交互乡括。我們能在 tests.py 甚至是 shell 中使用它肃廓。

我們依照慣例從 shell 開始,首先我們要做一些在 tests.py 里不是必須的準(zhǔn)備工作诲泌。第一步是在 shell 中配置測試環(huán)境:

>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

setup_test_environment() 提供了一個模板渲染器盲赊,允許我們?yōu)?responses 添加一些額外的屬性,例如 response.context敷扫,未安裝此 app 無法使用此功能哀蘑。注意,這個方法并 不會 配置測試數(shù)據(jù)庫,所以接下來的代碼將會在當(dāng)前存在的數(shù)據(jù)庫上運(yùn)行绘迁,輸出的內(nèi)容可能由于數(shù)據(jù)庫內(nèi)容的不同而不同合溺。如果你的 settings.py 中關(guān)于 TIME_ZONE 的設(shè)置不對,你可能無法獲取到期望的結(jié)果缀台。如果你之前忘了設(shè)置棠赛,在繼續(xù)之前檢查一下。

然后我們需要導(dǎo)入 django.test.TestCase 類(在后續(xù) tests.py 的實(shí)例中我們將會使用 django.test.TestCase 類膛腐,這個類里包含了自己的 client 實(shí)例睛约,所以不需要這一步):

>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()

搞定了之后,我們可以要求 client 為我們工作了:

image.png

改善視圖代碼
現(xiàn)在的投票列表會顯示將來的投票( pub_date 值是未來的某天)哲身。我們來修復(fù)這個問題痰腮。

在上一篇文章里,我們介紹了基于 ListView 的視圖類:

polls/views.py

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]

我們需要改進(jìn) get_queryset() 方法律罢,讓他它能通過將 Question 的 pub_data 屬性與 timezone.now() 相比較來判斷是否應(yīng)該顯示此 Question。首先我們需要一行 import 語句:

polls/views.py

from django.utils import timezone

然后我們把 get_queryset 方法改寫成下面這樣:

polls/views.py

def get_queryset(self):
    """
    Return the last five published questions (not including those set to be
    published in the future).
    """
    return Question.objects.filter(
        pub_date__lte=timezone.now()
    ).order_by('-pub_date')[:5]

測試新視圖

啟動服務(wù)器棍丐、在瀏覽器中載入站點(diǎn)误辑、創(chuàng)建一些發(fā)布時(shí)間在過去和將來的 Questions ,然后檢驗(yàn)只有已經(jīng)發(fā)布的 Questions 會展示出來歌逢,現(xiàn)在你可以對自己感到滿意了巾钉。你不想每次修改可能與這相關(guān)的代碼時(shí)都重復(fù)這樣做 —— 所以讓我們基于以上 shell 會話中的內(nèi)容,再編寫一個測試秘案。

將下面的代碼添加到 polls/tests.py :

polls/tests.py

from django.urls import reverse

然后我們寫一個公用的快捷函數(shù)用于創(chuàng)建投票問題砰苍,再為視圖創(chuàng)建一個測試類:

polls/tests.py

def create_question(question_text, days):
    """
    Create a question with the given `question_text` and published the
    given number of `days` offset to now (negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)


class QuestionIndexViewTests(TestCase):
    def test_no_questions(self):
        """
        If no questions exist, an appropriate message is displayed.
        """
        response = self.client.get(reverse('polls:index'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_past_question(self):
        """
        Questions with a pub_date in the past are displayed on the
        index page.
        """
        create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question.>']
        )

    def test_future_question(self):
        """
        Questions with a pub_date in the future aren't displayed on
        the index page.
        """
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_future_question_and_past_question(self):
        """
        Even if both past and future questions exist, only past questions
        are displayed.
        """
        create_question(question_text="Past question.", days=-30)
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question.>']
        )

    def test_two_past_questions(self):
        """
        The questions index page may display multiple questions.
        """
        create_question(question_text="Past question 1.", days=-30)
        create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question 2.>', '<Question: Past question 1.>']
        )

功能詳解:

首先是一個快捷函數(shù) create_question哈雏,它封裝了創(chuàng)建投票的流程第焰,減少了重復(fù)代碼论矾。

test_no_questions 方法里沒有創(chuàng)建任何投票献酗,它檢查返回的網(wǎng)頁上有沒有 "No polls are available." 這段消息和 latest_question_list 是否為空还最。注意到 django.test.TestCase 類提供了一些額外的 assertion 方法长已,在這個例子中郎嫁,我們使用了 assertContains() 和 assertQuerysetEqual() 堕花。

在 test_past_question 方法中未舟,我們創(chuàng)建了一個投票并檢查它是否出現(xiàn)在列表中圈暗。

在 test_future_question 中,我們創(chuàng)建 pub_date 在未來某天的投票裕膀。數(shù)據(jù)庫會在每次調(diào)用測試方法前被重置员串,所以第一個投票已經(jīng)沒了,所以主頁中應(yīng)該沒有任何投票昼扛。

剩下的那些也都差不多寸齐。實(shí)際上,測試就是假裝一些管理員的輸入,然后通過用戶端的表現(xiàn)是否符合預(yù)期來判斷新加入的改變是否破壞了原有的系統(tǒng)狀態(tài)访忿。

測試 DetailView

我們的工作似乎已經(jīng)很完美了瞧栗?不,還有一個問題:就算在發(fā)布日期時(shí)未來的那些投票不會在目錄頁 index 里出現(xiàn)海铆,但是如果用戶知道或者猜到正確的 URL 迹恐,還是可以訪問到它們。所以我們得在 DetailView 里增加一些約束:

polls/views.py

class DetailView(generic.DetailView):
    ...
    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Question.objects.filter(pub_date__lte=timezone.now())

當(dāng)然卧斟,我們將增加一些測試來檢驗(yàn) pub_date 在過去的 Question 可以顯示出來殴边,而 pub_date 在未來的不可以:

polls/tests.py

class QuestionDetailViewTests(TestCase):
    def test_future_question(self):
        """
        The detail view of a question with a pub_date in the future
        returns a 404 not found.
        """
        future_question = create_question(question_text='Future question.', days=5)
        url = reverse('polls:detail', args=(future_question.id,))
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_past_question(self):
        """
        The detail view of a question with a pub_date in the past
        displays the question's text.
        """
        past_question = create_question(question_text='Past Question.', days=-5)
        url = reverse('polls:detail', args=(past_question.id,))
        response = self.client.get(url)
        self.assertContains(response, past_question.question_text)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市珍语,隨后出現(xiàn)的幾起案子锤岸,更是在濱河造成了極大的恐慌,老刑警劉巖板乙,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件是偷,死亡現(xiàn)場離奇詭異,居然都是意外死亡募逞,警方通過查閱死者的電腦和手機(jī)蛋铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來放接,“玉大人刺啦,你說我怎么就攤上這事【榔ⅲ” “怎么了玛瘸?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長苟蹈。 經(jīng)常有香客問我糊渊,道長,這世上最難降的妖魔是什么慧脱? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任再来,我火速辦了婚禮,結(jié)果婚禮上磷瘤,老公的妹妹穿的比我還像新娘芒篷。我一直安慰自己,他們只是感情好采缚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布针炉。 她就那樣靜靜地躺著,像睡著了一般扳抽。 火紅的嫁衣襯著肌膚如雪篡帕。 梳的紋絲不亂的頭發(fā)上殖侵,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機(jī)與錄音镰烧,去河邊找鬼拢军。 笑死,一個胖子當(dāng)著我的面吹牛怔鳖,可吹牛的內(nèi)容都是我干的茉唉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼结执,長吁一口氣:“原來是場噩夢啊……” “哼度陆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起献幔,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤懂傀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蜡感,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹬蚁,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年郑兴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缚忧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡杈笔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出糕非,到底是詐尸還是另有隱情蒙具,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布朽肥,位于F島的核電站禁筏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏衡招。R本人自食惡果不足惜篱昔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望始腾。 院中可真熱鬧州刽,春花似錦、人聲如沸浪箭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奶栖。三九已至匹表,卻和暖如春门坷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袍镀。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工默蚌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苇羡。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓绸吸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宣虾。 傳聞我的和親對象是個殘疾皇子惯裕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容