python+requests接口自動化測試框架實例詳解教程

前段時間由于公司測試方向的轉型磷瘤,由原來的web頁面功能測試轉變成接口測試,之前大多都是手工進行搜变,利用postman和jmeter進行的接口測試采缚,后來,組內有人講原先web自動化的測試框架移駕挠他,搭建成接口的自動化框架扳抽,使用的是java語言。對于一個不會Java的小伙伴殖侵,怎樣完成自動化測試呢贸呢?

今天,就和大家分享一下我自己用Python寫的接口自動化測試框架吧拢军,沒有Java基礎的小伙伴也能快速上手哦楞陷。
如果有想要學習Python或者正在學習Python中的小伙伴,需要學習資料的話茉唉,可以到我的微信公眾號:Python學習知識圈固蛾,后臺回復:“01”结执,即可拿Python學習資料

1、構建思路

正常的接口測試流程是什么艾凯?

腦海里的反應是不是這樣的:確定測試接口的工具 —> 配置需要的接口參數(shù) —> 進行測試 —> 檢查測試結果(有的需要數(shù)據(jù)庫輔助) —> 生成測試報告(html報告)

根據(jù)這個過程献幔,我們一步步來搭建框架。在這個過程中趾诗,我們需要做到業(yè)務和數(shù)據(jù)的分離蜡感,這樣才能靈活,達到我們寫框架的目的恃泪。只要好好做郑兴,一定可以成功。這也是我當初對自己說的贝乎。

2杈笔、結構劃分

我的結構是這樣的,大家可以參考下:

image

common:存放一些共通的方法
result:執(zhí)行過程中生成的文件夾糕非,里面存放每次測試的結果
testCase:用于存放具體的測試case
testFile:存放測試過程中用到的文件,包括上傳的文件球榆,測試用例以及 數(shù)據(jù)庫的sql語句
caselist:txt文件朽肥,配置每次執(zhí)行的case名稱
config:配置一些常量,例如數(shù)據(jù)庫的相關信息持钉,接口的相關信息等
readConfig: 用于讀取config配置文件中的內容
runAll:用于執(zhí)行case

既然整體結構有了劃分衡招,接下來就該一步步的填充整個框架了,首先每强,我們先來看看config.ini和readConfig.py兩個文件始腾,從他們入手,個人覺得比較容易走下去噠空执。

3浪箭、配置文件
我們來看下文件的內容是什么樣子的:

[DATABASE]
host = 50.23.190.57
username = xxxxxx
password = ******
port = 3306
database = databasename

[HTTP]
# 接口的url
baseurl = http://xx.xxxx.xx 
port = 8080
timeout = 1.0

[EMAIL]
mail_host = smtp.163.com
mail_user = xxx@163.com
mail_pass = *********
mail_port = 25
sender = xxx@163.com
receiver = xxxx@qq.com/xxxx@qq.com
subject = python
content = "All interface test has been complited\nplease read the report file about the detile of result in the attachment."
testuser = Someone
on_off = 1

相信大家都知道這樣的配置文件,沒錯辨绊,所有一成不變的東西奶栖,我們都可以放到這里來。哈哈门坷,怎么樣宣鄙,不錯吧。

4默蚌、運用get方法讀取配置文件

現(xiàn)在冻晤,我們已經做好了固定的“倉庫”,來保存我們平時不動的東西绸吸。那么鼻弧,我們要怎么把它拿出來為我所用呢设江?這時候,readConfig.py文件出世了温数,它成功的幫我們解決了這個問題绣硝,下面就讓我們來一睹它的廬山真面目吧。

import os
import codecs
import configparser

proDir = os.path.split(os.path.realpath(__file__))[0]
configPath = os.path.join(proDir, "config.ini")

class ReadConfig:
    def __init__(self):
        fd = open(configPath)
        data = fd.read()

        #  remove BOM
        if data[:3] == codecs.BOM_UTF8:
            data = data[3:]
            file = codecs.open(configPath, "w")
            file.write(data)
            file.close()
        fd.close()

        self.cf = configparser.ConfigParser()
        self.cf.read(configPath)

    def get_email(self, name):
        value = self.cf.get("EMAIL", name)
        return value

    def get_http(self, name):
        value = self.cf.get("HTTP", name)
        return value

    def get_db(self, name):
        value = self.cf.get("DATABASE", name)
        return value

怎么樣撑刺,是不是看著很簡單啊鹉胖,我們定義的方法:根據(jù)名稱取對應的值。是不是so easy够傍?甫菠!當然了,這里我們只用到了get方法冕屯,還有其他的例如:set方法寂诱,有興趣的同學可以自己去探索下。

話不多說安聘,我們先來看下common到底有哪些東西痰洒。

image

既然配置文件和讀取配置文件我們都已經完成了,也看到了common里的內容浴韭,接下來就可以寫common里的共通方法了丘喻,從哪個下手呢?今天念颈,我們就來翻“Log.py”的牌吧泉粉,因為它是比較獨立的,我們單獨跟他打交道榴芳,也為了以后它能為我們服務打下良好基礎嗡靡。

5、common文件的共通文件

1)log文件

這里呢窟感,我想跟大家多說兩句讨彼,對于這個log文件呢,我給它單獨啟用了一個線程柿祈,這樣在整個運行過程中点骑,我們在寫log的時候也會比較方便,看名字大家也知道了谍夭,這里就是我們對輸出的日志的所有操作了黑滴,主要是對輸出格式的規(guī)定,輸出等級的定義以及其他一些輸出的定義等等紧索。
總之袁辈,你想對log做的任何事情,都可以放到這里來珠漂。我們來看下代碼晚缩,沒有比這個更直接有效的了尾膊。

import logging
from datetime import datetime
import threading

首先,我們要像上面那樣荞彼,引入需要的模塊冈敛,才能進行接下來的操作。

class Log:
    def __init__(self):
        global logPath, resultPath, proDir
        proDir = readConfig.proDir
        resultPath = os.path.join(proDir, "result")
        # create result file if it doesn't exist
        if not os.path.exists(resultPath):
            os.mkdir(resultPath)
        # defined test result file name by localtime
        logPath = os.path.join(resultPath, str(datetime.now().strftime("%Y%m%d%H%M%S")))
        # create test result file if it doesn't exist
        if not os.path.exists(logPath):
            os.mkdir(logPath)
        # defined logger
        self.logger = logging.getLogger()
        # defined log level
        self.logger.setLevel(logging.INFO)

        # defined handler
        handler = logging.FileHandler(os.path.join(logPath, "output.log"))
        # defined formatter
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        # defined formatter
        handler.setFormatter(formatter)
        # add handler
        self.logger.addHandler(handler)

現(xiàn)在鸣皂,我們創(chuàng)建了上面的Log類抓谴,在init初始化方法中,我們進行了log的相關初始化操作寞缝。這樣癌压,log的基本格式已經定義完成了,至于其他的方法荆陆,就靠大家自己發(fā)揮了滩届,畢竟每個人的需求也不同,我們就只寫普遍的共用方法啦被啼。

接下來帜消,就是把它放進一個線程內了,請看下面的代碼:

class MyLog:
    log = None
    mutex = threading.Lock()

    def __init__(self):
        pass

    @staticmethod
    def get_log():

        if MyLog.log is None:
            MyLog.mutex.acquire()
            MyLog.log = Log()
            MyLog.mutex.release()

        return MyLog.log

看起來是不是沒有想象中的那樣復雜芭ㄌ濉泡挺?哈哈,就是這樣簡單汹碱。用python做測試比java要簡單許多。這也是我為什么選擇它的原因荞估。

2)配置接口文件

下面咳促,我們繼續(xù)搭建,這次要做的勘伺,是configHttp.py的內容跪腹。沒錯,我們開始配置接口文件啦7勺怼(終于寫到接口了冲茸,是不是很開心啊~)

下面是接口文件中主要部分的內容,讓我們一起來看看吧缅帘。

import requests
import readConfig as readConfig
from common.Log import MyLog as Log

localReadConfig = readConfig.ReadConfig()

class ConfigHttp:
    def __init__(self):
        global host, port, timeout
        host = localReadConfig.get_http("baseurl")
        port = localReadConfig.get_http("port")
        timeout = localReadConfig.get_http("timeout")
        self.log = Log.get_log()
        self.logger = self.log.get_logger()
        self.headers = {}
        self.params = {}
        self.data = {}
        self.url = None
        self.files = {}

    def set_url(self, url):
        self.url = host + url

    def set_headers(self, header):
        self.headers = header

    def set_params(self, param):
        self.params = param

    def set_data(self, data):
        self.data = data

    def set_files(self, file):
        self.files = file

    # defined http get method
    def get(self):
        try:
            response = requests.get(self.url, params=self.params, headers=self.headers, timeout=float(timeout))
            # response.raise_for_status()
            return response
        except TimeoutError:
            self.logger.error("Time out!")
            return None

    # defined http post method
    def post(self):
        try:
            response = requests.post(self.url, headers=self.headers, data=self.data, files=self.files, timeout=float(timeout))
            # response.raise_for_status()
            return response
        except TimeoutError:
            self.logger.error("Time out!")
            return None

這里我們就挑重點來說吧轴术。首先,可以看到钦无,小編這次是用Python自帶的requests來進行接口測試的逗栽,相信有心的朋友已經看出來了,Python+requests這個模式是很好用的失暂,它已經幫我們封裝好了測試接口的方法彼宠,用起來很方便鳄虱。這里呢,我就拿get和post兩個方法來說吧凭峡。(平時用的最多的就是這兩個方法了拙已,其他方法,大家可以仿照著自行擴展)

get方法

接口測試中見到最多的就是get方法和post方法摧冀,其中倍踪,get方法用于獲取接口的測試,說白了按价,就是使用get的接口惭适,不會對后臺數(shù)據(jù)進行更改。

對于requests提供的get方法楼镐,有幾個常用的參數(shù):

url:顯而易見癞志,就是接口的地址url啦

headers:定制請求頭(headers),例如:content-type = application/x-www-form-urlencoded

params:用于傳遞測試接口所要用的參數(shù)框产,這里我們用Python中的字典形式(key:value)進行參數(shù)的傳遞

timeout:設置接口連接的最大時間(超過該時間會拋出超時錯誤)

現(xiàn)在凄杯,各個參數(shù)我們已經知道是什么意思了,剩下的就是往里面填值啦秉宿,是不是機械式的應用啊戒突,哈哈,小編我就是這樣機械般的學習的啦~

舉個栗子:

url=‘http://api.shein.com/v2/member/logout’
header={‘content-type’: application/x-www-form-urlencoded}
param={‘user_id’: 123456,‘email’: 123456@163.com}
timeout=0.5
requests.get(url, headers=header, params=param, timeout=timeout)

post方法

與get方法類似描睦,只要設置好對應的參數(shù)膊存,就可以了。下面就直接舉個栗子忱叭,直接上代碼吧:

url=‘http://api.shein.com/v2/member/login’
header={‘content-type’: application/x-www-form-urlencoded}
data={‘email’: 123456@163.com,‘password’: 123456}
timeout=0.5
requests.post(url, headers=header, data=data, timeout=timeout)

怎么樣隔崎,是不是也很簡單啊。這里我們需要說明一下韵丑,post方法中的參數(shù)爵卒,我們不在使用params進行傳遞,而是改用data進行傳遞了撵彻。

6钓株、接口返回值

1)常用返回值

哈哈哈,終于說完啦陌僵,下面我們來探討下接口的返回值轴合。依然只說常用的返回值的操作。

text:獲取接口返回值的文本格式

json():獲取接口返回值的json()格式

status_code:返回狀態(tài)碼(成功為:200)

headers:返回完整的請求頭信息(headers['name']:返回指定的headers內容)
encoding:返回字符編碼格式

url:返回接口的完整url地址

以上這些碗短,就是常用的方法啦值桩,大家可自行取之。

2)拋出異常的解決辦法

關于失敗請求拋出異常豪椿,我們可以使用“raise_for_status()”來完成奔坟,那么携栋,當我們的請求發(fā)生錯誤時,就會拋出異常咳秉。在這里提醒下各位朋友婉支,如果你的接口,在地址不正確的時候澜建,會有相應的錯誤提示(有時也需要進行測試)向挖,這時,千萬不能使用這個方法來拋出錯誤炕舵,因為Python自己在鏈接接口時就已經把錯誤拋出何之,那么,后面你將無法測試期望的內容咽筋。而且程序會直接在這里當?shù)羧芡疲藻e誤來計。(別問我怎么知道的奸攻,因為我就是測試的時候發(fā)現(xiàn)的)

7蒜危、common.py的內容

好了。接口文件也講完了睹耐,是不是感覺離成功不遠了呢辐赞?嗯,如果各位已經看到了這里硝训,那么恭喜大家响委,下面還有很長的路要走~

下面,我們一起來學習common.py里的內容窖梁。

import os
from xlrd import open_workbook
from xml.etree import ElementTree as ElementTree
from common.Log import MyLog as Log

localConfigHttp = configHttp.ConfigHttp()
log = Log.get_log()
logger = log.get_logger()

# 從excel文件中讀取測試用例
def get_xls(xls_name, sheet_name):
    cls = []
    # get xls file's path
    xlsPath = os.path.join(proDir, "testFile", xls_name)
    # open xls file
    file = open_workbook(xlsPath)
    # get sheet by name
    sheet = file.sheet_by_name(sheet_name)
    # get one sheet's rows
    nrows = sheet.nrows
    for i in range(nrows):
        if sheet.row_values(i)[0] != u'case_name':
            cls.append(sheet.row_values(i))
    return cls

# 從xml文件中讀取sql語句
database = {}
def set_xml():
    if len(database) == 0:
        sql_path = os.path.join(proDir, "testFile", "SQL.xml")
        tree = ElementTree.parse(sql_path)
        for db in tree.findall("database"):
            db_name = db.get("name")
            # print(db_name)
            table = {}
            for tb in db.getchildren():
                table_name = tb.get("name")
                # print(table_name)
                sql = {}
                for data in tb.getchildren():
                    sql_id = data.get("id")
                    # print(sql_id)
                    sql[sql_id] = data.text
                table[table_name] = sql
            database[db_name] = table

def get_xml_dict(database_name, table_name):
    set_xml()
    database_dict = database.get(database_name).get(table_name)
    return database_dict

def get_sql(database_name, table_name, sql_id):
    db = get_xml_dict(database_name, table_name)
    sql = db.get(sql_id)
    return sql

上面就是我們common的兩大主要內容了赘风,什么?還不知道是什么嗎窄绒?讓我告訴你吧贝次。

1.我們利用xml.etree.Element來對xml文件進行操作崔兴,然后通過我們自定義的方法彰导,根據(jù)傳遞不同的參數(shù)取得不(想)同(要)的值。

2.利用xlrd來操作excel文件敲茄,注意啦位谋,我們是用excel文件來管理測試用例的。

聽起來會不會有點兒懵堰燎,小編剛學時也很懵掏父,看文件就好理解了。

excel文件:

image

xml文件:

image

至于具體的方法秆剪,我就不再贅述了赊淑。

8爵政、數(shù)據(jù)庫

接下來,我們看看數(shù)據(jù)庫和發(fā)送郵件吧(也可根據(jù)需要陶缺,不寫該部分內容)钾挟。先看老朋友“數(shù)據(jù)庫”吧,小編這次使用的是MySQL數(shù)據(jù)庫饱岸,所以我們就以它為例吧掺出。

import pymysql
import readConfig as readConfig
from common.Log import MyLog as Log

localReadConfig = readConfig.ReadConfig()

class MyDB:
    global host, username, password, port, database, config
    host = localReadConfig.get_db("host")
    username = localReadConfig.get_db("username")
    password = localReadConfig.get_db("password")
    port = localReadConfig.get_db("port")
    database = localReadConfig.get_db("database")
    config = {
        'host': str(host),
        'user': username,
        'passwd': password,
        'port': int(port),
        'db': database
    }

    def __init__(self):
        self.log = Log.get_log()
        self.logger = self.log.get_logger()
        self.db = None
        self.cursor = None

    def connectDB(self):
        try:
            # connect to DB
            self.db = pymysql.connect(**config)
            # create cursor
            self.cursor = self.db.cursor()
            print("Connect DB successfully!")
        except ConnectionError as ex:
            self.logger.error(str(ex))

    def executeSQL(self, sql, params):
        self.connectDB()
        # executing sql
        self.cursor.execute(sql, params)
        # executing by committing to DB
        self.db.commit()
        return self.cursor

    def get_all(self, cursor):
        value = cursor.fetchall()
        return value

    def get_one(self, cursor):
        value = cursor.fetchone()
        return value

    def closeDB(self):
        self.db.close()
        print("Database closed!")

這就是完整的數(shù)據(jù)庫的文件啦。因為小編的需求對數(shù)據(jù)庫的操作不是很復雜苫费,所以這些已基本滿足要求啦汤锨。注意下啦,在此之前百框,請朋友們先把pymysql裝起來闲礼!

安裝的方法很簡單,由于小編是使用pip來管理Python包安裝的琅翻,所以只要進入Python安裝路徑下的pip文件夾下位仁,執(zhí)行以下命令即可:

pip install pymysql

這樣,我們就可以利用Python鏈接數(shù)據(jù)庫啦~

小伙伴們發(fā)現(xiàn)沒方椎,在整個文件中聂抢,我們并沒有出現(xiàn)具體的變量值哦,為什么呢棠众?沒錯琳疏,因為前面我們寫了config.ini文件,所有的數(shù)據(jù)庫配置信息都在這個文件內哦闸拿,是不是感覺很方便呢空盼,以后就算變更數(shù)據(jù)庫了,也只要修改config.ini文件的內容就可以了新荤,結合前面測試用例的管理(excel文件)揽趾,sql語句的存放(xml文件),還有接下來我們要說的苛骨,businessCommon.py和存放具體case的文件夾篱瞎,那么我們就已經將數(shù)據(jù)和業(yè)務分開啦,哈哈哈痒芝,想想以后修改測試用例內容俐筋,sql語句神馬的工作,再也不用每個case都修改严衬,只要改幾個固定的文件澄者,是不是頓時開心了呢?

總結:

回歸上面的configDB.py文件,內容很簡單粱挡,相信大家都能看得懂赠幕,就是連接數(shù)據(jù)庫,執(zhí)行sql询筏,獲取結果劣坊,最后關閉數(shù)據(jù)庫,沒有什么不一樣的地方屈留。

再來談談郵件局冰。你是不是也遇到過這樣的問題:每次測試完之后,都需要給開發(fā)一份測試報告灌危。那么康二,對于我這樣的懶人,是不愿意老是找人家開發(fā)的勇蝙,所以沫勿,我就想,每次測試完味混,我們可以讓程序自己給開發(fā)人員發(fā)一封e-mail产雹,告訴他們,測試已經結束了翁锡,并且把測試報告以附件的形式蔓挖,通過e-mail發(fā)送給開發(fā)者的郵箱,這樣效率迅速提升馆衔。

所以瘟判,configEmail.py應運而生。請看:

import os
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from datetime import datetime
import threading
import readConfig as readConfig
from common.Log import MyLog
import zipfile
import glob

localReadConfig = readConfig.ReadConfig()

class Email:
    def __init__(self):
        global host, user, password, port, sender, title, content
        host = localReadConfig.get_email("mail_host")
        user = localReadConfig.get_email("mail_user")
        password = localReadConfig.get_email("mail_pass")
        port = localReadConfig.get_email("mail_port")
        sender = localReadConfig.get_email("sender")
        title = localReadConfig.get_email("subject")
        content = localReadConfig.get_email("content")
        self.value = localReadConfig.get_email("receiver")
        self.receiver = []
        # get receiver list
        for n in str(self.value).split("/"):
            self.receiver.append(n)
        # defined email subject
        date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.subject = title + " " + date
        self.log = MyLog.get_log()
        self.logger = self.log.get_logger()
        self.msg = MIMEMultipart('mixed')

    def config_header(self):
        self.msg['subject'] = self.subject
        self.msg['from'] = sender
        self.msg['to'] = ";".join(self.receiver)

    def config_content(self):
        content_plain = MIMEText(content, 'plain', 'utf-8')
        self.msg.attach(content_plain)

    def config_file(self):
        # if the file content is not null, then config the email file
        if self.check_file():

            reportpath = self.log.get_result_path()
            zippath = os.path.join(readConfig.proDir, "result", "test.zip")
            # zip file
            files = glob.glob(reportpath + '\*')
            f = zipfile.ZipFile(zippath, 'w', zipfile.ZIP_DEFLATED)
            for file in files:
                f.write(file)
            f.close()

            reportfile = open(zippath, 'rb').read()
            filehtml = MIMEText(reportfile, 'base64', 'utf-8')
            filehtml['Content-Type'] = 'application/octet-stream'
            filehtml['Content-Disposition'] = 'attachment; filename="test.zip"'
            self.msg.attach(filehtml)

    def check_file(self):
        reportpath = self.log.get_report_path()
        if os.path.isfile(reportpath) and not os.stat(reportpath) == 0:
            return True
        else:
            return False

    def send_email(self):
        self.config_header()
        self.config_content()
        self.config_file()
        try:
            smtp = smtplib.SMTP()
            smtp.connect(host)
            smtp.login(user, password)
            smtp.sendmail(sender, self.receiver, self.msg.as_string())
            smtp.quit()
            self.logger.info("The test report has send to developer by email.")
        except Exception as ex:
            self.logger.error(str(ex))

class MyEmail:
    email = None
    mutex = threading.Lock()

    def __init__(self):
        pass

    @staticmethod
    def get_email():

        if MyEmail.email is None:
            MyEmail.mutex.acquire()
            MyEmail.email = Email()
            MyEmail.mutex.release()
        return MyEmail.email

if __name__ == "__main__":
    email = MyEmail.get_email()
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末角溃,一起剝皮案震驚了整個濱河市拷获,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌减细,老刑警劉巖匆瓜,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異未蝌,居然都是意外死亡驮吱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門树埠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糠馆,“玉大人嘶伟,你說我怎么就攤上這事怎憋。” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵绊袋,是天一觀的道長毕匀。 經常有香客問我,道長癌别,這世上最難降的妖魔是什么皂岔? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮展姐,結果婚禮上躁垛,老公的妹妹穿的比我還像新娘。我一直安慰自己圾笨,他們只是感情好教馆,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著擂达,像睡著了一般土铺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上板鬓,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天悲敷,我揣著相機與錄音,去河邊找鬼俭令。 笑死后德,一個胖子當著我的面吹牛,可吹牛的內容都是我干的抄腔。 我是一名探鬼主播探遵,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妓柜!你這毒婦竟也來了箱季?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤棍掐,失蹤者是張志新(化名)和其女友劉穎藏雏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體作煌,經...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡掘殴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了粟誓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奏寨。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鹰服,靈堂內的尸體忽然破棺而出病瞳,到底是詐尸還是另有隱情揽咕,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布套菜,位于F島的核電站亲善,受9級特大地震影響,放射性物質發(fā)生泄漏逗柴。R本人自食惡果不足惜蛹头,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望戏溺。 院中可真熱鬧渣蜗,春花似錦、人聲如沸旷祸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揭芍。三九已至,卻和暖如春饭聚,著一層夾襖步出監(jiān)牢的瞬間嫌吠,已是汗流浹背止潘。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辫诅,地道東北人凭戴。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像炕矮,于是被迫代替她去往敵國和親么夫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內容