API接口自動(dòng)化測試 - 新手搭建思路分享(Pytest)

一四瘫、背景介紹

  • 由于被測系統(tǒng)的復(fù)雜度不斷上升,導(dǎo)致傳統(tǒng)的測試方法成本上升且測試效率大幅下降;因此引入自動(dòng)化測試是必然趨勢悼沿。
    鑒于接口測試相對于UI測試更加穩(wěn)定,且相對容易自動(dòng)化持續(xù)集成骚灸;故以接口方式作為自動(dòng)化項(xiàng)目切入糟趾,主要目的如下:
  • 1、用于回歸測試甚牲,解決手工回歸測試帶來的繁瑣且重復(fù)工作;
  • 2义郑、線上或線下巡檢測試,結(jié)合jenkins部署持續(xù)集成丈钙,及時(shí)發(fā)現(xiàn)運(yùn)行環(huán)境存在的問題;
  • 3非驮、提升團(tuán)隊(duì)自動(dòng)化測試能力,為系統(tǒng)業(yè)務(wù)提供強(qiáng)有力的測試手段雏赦;

二劫笙、構(gòu)思設(shè)計(jì)及主要實(shí)現(xiàn)點(diǎn)

2.2)、需實(shí)現(xiàn)的功要點(diǎn):

1喉誊、 可持續(xù)集成采用方式:構(gòu)建部署 gitlab + jenkins+ docker 方式
2邀摆、 自動(dòng)化執(zhí)行-觸發(fā)點(diǎn)多元化:
2.1)、API接口方式:通過接口字段傳參方式伍茄,觸發(fā)自動(dòng)化測試用例執(zhí)行栋盹;
2.2)、jenkins構(gòu)建部署方式:通過被測對象構(gòu)建時(shí)敷矫,流水線觸發(fā)自動(dòng)化測試用例執(zhí)行例获;
2.3)汉额、命令行方式:在部署的docke容器內(nèi)或者本地開發(fā)調(diào)試時(shí),通過命令行方式觸發(fā)執(zhí)行榨汤;
3蠕搜、 編程代碼與測試數(shù)據(jù)分離:自動(dòng)化服務(wù)部署后,因測試用例數(shù)據(jù)仍是高頻操作模塊收壕,所以代碼和測試數(shù)據(jù)是需要做分離的妓灌,整塊測試數(shù)據(jù)以yaml方式設(shè)計(jì)管理;
4蜜宪、 高頻的用例數(shù)據(jù)及文件處理方式:
4.1)虫埂、可通過前端頁面交互方式,支持對測試用例文件上傳圃验、下載操作掉伏;
4.2)、可通過API接口方式對用例數(shù)據(jù)進(jìn)行增刪改查(即編輯數(shù)據(jù)無需進(jìn)入docker容器內(nèi)修改澳窑,更加便捷)斧散;
5、 緩存機(jī)制處理:主要對于賬號登錄信息(如token)摊聋、被測接口有依賴的前置數(shù)據(jù)鸡捐、流程用例方面依賴接口的數(shù)據(jù)等,需要做緩存機(jī)制處理栗精;
6闯参、 測試報(bào)告功能:為了便于項(xiàng)目所有人查看,測試報(bào)告需支持在線鏈接查看(此塊引用Allure服務(wù))悲立;
說明:此塊在線 Allure server是引用GitHub上的大佬(kochetkovma),有需要可直接去搜索下
7鹿寨、 日志記錄留痕:基本的日志記錄信息,這是常規(guī)必備點(diǎn)薪夕。

2.2)脚草、接口自動(dòng)化測試-架構(gòu)圖:

  • 其實(shí)看這張圖就足夠了,表達(dá)的思路都是這里原献,也畫了我差不多一天時(shí)間馏慨,吐血~


    接口自動(dòng)化架構(gòu)圖.png

三、直接看最后的成果(以觸發(fā)點(diǎn)1和2為例):

1姑隅、觸發(fā)點(diǎn)1:通過接口請求觸發(fā)(引用postman工具):

  • 步驟1写隶、調(diào)用觸發(fā)執(zhí)行接口:傳入執(zhí)行命令(以pytest命令為基礎(chǔ));如圖“-m smoke_flow_one -s”只執(zhí)行指定標(biāo)簽的用例讲仰;
  • 步驟2慕趴、執(zhí)行接口響應(yīng):接口返回本次執(zhí)行用例的狀態(tài)(msg字段:通過或不通過)以及測試報(bào)告鏈接(data字段);
  • 步驟3、測試報(bào)告:打開鏈接可查看到本次執(zhí)行用例的詳細(xì)情況冕房;
    步驟1躏啰、postman接口調(diào)用.png

    步驟2、通過接口返回鏈接打開測試報(bào)告.png

    步驟3耙册、查看本次測試執(zhí)行情況

2给僵、觸發(fā)點(diǎn)2:被測對象jar包更新-觸發(fā)執(zhí)行(jenkins):

  • 步驟1、jenkins流水線配置:被測服務(wù)在流水線配置中添加觸發(fā)自動(dòng)化測試節(jié)點(diǎn)详拙,在被測對象部署完畢后會(huì)進(jìn)入自動(dòng)化執(zhí)行環(huán)節(jié)帝际;
  • 步驟2、流水線節(jié)點(diǎn):根據(jù)自動(dòng)化測試節(jié)點(diǎn)判斷本次構(gòu)建是否測試通過溪厘,且可通過jenkins logs內(nèi)的鏈接查看完整測試報(bào)告胡本;
  • 步驟3、測試報(bào)告:打開鏈接查看本次執(zhí)行用例的詳細(xì)情況畸悬;
    步驟1、jenkins流水線增加觸發(fā)自動(dòng)化節(jié)點(diǎn)

    jenkins logs獲取鏈接查看報(bào)告

四珊佣、高頻操作的測試用例數(shù)據(jù)及文件處理:

4.1蹋宦、數(shù)據(jù)操作:通過接口調(diào)用修改用例數(shù)據(jù)-->>增刪改查(如下以“查”和“改”為示例):
  • 步驟1、根據(jù)傳入的類型字段及指定文件咒锻,可查詢用例數(shù)據(jù):
    1冷冗、查詢-指定用例數(shù)據(jù)
  • 步驟2、根據(jù)傳入的字段參數(shù)惑艇,可修改對應(yīng)的用例數(shù)據(jù):
    2蒿辙、修改-指定用例數(shù)據(jù)
4.2、文件操作:以頁面形式對用例文件進(jìn)行上傳/下載操作(水平有限滨巴,只能搞簡陋版的html頁面了):
  • 步驟1思灌、對文件上傳:對已修改的用例文件或者需要新增文件,可上傳到自定義指定目錄恭取;

    用例文件上傳.png

  • 步驟2泰偿、對文件下載:通過html頁面進(jìn)入各目錄,可自定義選擇文件下載蜈垮;

    用例文件下載.png

四耗跛、自動(dòng)化項(xiàng)目結(jié)構(gòu)-示例( 因瑣碎點(diǎn)太多,只抽取主流點(diǎn)作為說明)

  • 作為一個(gè)測試編程新手攒发,比不了專業(yè)的開發(fā)人員调塌;各位大佬,如有優(yōu)化或其他構(gòu)思思路歡迎指點(diǎn)評論惠猿,我再調(diào)整優(yōu)化羔砾;

4.1、項(xiàng)目目錄結(jié)構(gòu)說明:

代碼目錄結(jié)構(gòu)

4.2、入口路由方面(以觸發(fā)1為例)-->示例:

1)蜒茄、入口(Flask框架):摘取server.py一小段接口樣例

'''摘取server.py其中一個(gè)接口'''
app = Flask(__name__)
@app.route("/autotest/runcase", methods=['POST'])
def run_auto_test():
    """運(yùn)行自動(dòng)化:return:"""
    try:
        command = request.json.get("command").strip()
    except AttributeError:
        return jsonify({"code": -1, 'msg': '缺少必要參數(shù):command'})
    # executor.submit(__run_jobs, run_command)
    re_data, re_start = run_py(['0', command])
    re_msg = '通過' if re_start == 0 else '不通過'
    return jsonify({"code": re_start, "msg": "自動(dòng)化測試:" + re_msg, "data": re_data})
'''摘取runner.py部分中pytest命令處理模塊'''
lock = threading.Lock()
def run_py(cmd_system):
    case_config = Case_Config()
    now_time = time.strftime('%Y%m%d-%H%M%S')
    report_data = 'TestReport/report_data/{}/'.format(now_time)
    report_html = 'TestReport/html/{}/'.format(now_time)
    run_sys = 'pytest {} --alluredir={}  --clean-alluredir'.format('Testsuites/TestCase/' + str(cmd_system[1]),
                                                                   report_data)
    with lock:  # 加鎖
        sys_start = os.system(run_sys)
        if sys_start != 0 and sys_start != 1 and sys_start != 256:
            sys_start_msg = f'自動(dòng)化執(zhí)行出現(xiàn)異常(start={sys_start})唉擂,本次執(zhí)行命令:{run_sys}'
            logger.error(sys_start_msg)
            return {"msg": sys_start_msg}, -1
    re_data = '1、執(zhí)行命令:run_sys=' + run_sys
    try:
        if cmd_system[2] in ('debug', 'false', False):
            run_allure = '{} generate {} -o {} --clean'.format(r'allure-2.17.0\bin\allure', report_data, report_html)
            re_data = re_data + '\n2檀葛、執(zhí)行命令:run_allure={};'.format(run_allure)
            os.system(run_allure)
    except IndexError:
        ip_port = case_config.yml_cf_data()['allure_ip_port']
        allure_report_url = AllureServer(ip_port).auto_report(report_data, now_time)
        re_data = {'報(bào)告鏈接地址': allure_report_url, "執(zhí)行命令": run_sys}
    test_result = case_config.yml_cf_data('Testsuites/TestData/com_data/test_result.yml', 'test_result')
    logger.info('***用例自動(dòng)化測試結(jié)果:' + test_result)
    re_start_code = 0 if test_result == 'pass' and sys_start == 0 else -1
    logger.info(re_data)
    return re_data, re_start_code

2)玩祟、摘取其中一段test_x.py測試用例代碼-->示例:

from Testsuites.TestModel import InitTestData
import pytest
import allure

USE_CASE_PATH = 'tp_zds_app/auto_getuser.yml'

@pytest.mark.run(order=131)
@allure.epic("Web-后端管理頁面")
@allure.feature("案例事務(wù)操作模塊")
@allure.story("推送案例")
class TestAutoGetUser:
    @classmethod
    def setup_class(cls):  # 類初始化(前置,全局一次)屿聋;  teardown_class(cls)=后置全局一次
        cls.init_data = InitTestData.init_test_data(USE_CASE_PATH, api_host='host_gateway', login_token=None,
                                                    pgsql_connect=True)
        cls.init_case_run = cls.init_data['case_run']
        cls.regist_no = cls.init_data['local_var']['regist_no']
        cls.sql_Pre = cls.init_data['local_var']['sql_Pre']

    def setup_method(self):  # 方法初始化
        print('\n === 測試案件前置動(dòng)作 ===')
        run_sql = self.init_data['regular_data'](self.sql_Pre, {'regist_no': self.regist_no})
        self.init_data['pgsql_db'](run_sql, 'delete')

    @pytest.mark.smoke_flow_four
    @pytest.mark.smoke_main
    def test_auto_getuser_1(self, set_global_data):
        case_data = self.init_data['case_yml_data']['case_001']  #定義哪一條用例
        request_data = case_data['request_data']['body']  #定義接口入?yún)?shù)據(jù)
        expect_data = case_data['expect_data']['response_data'] #定義斷言模塊
        status_code = case_data['expect_data']['status_code']  #定義接口狀態(tài)碼
        sql_check = self.init_data['regular_data'](case_data['other_data']['sql_check'], {'regist_no': self.regist_no})  #接口場景涉及的sql執(zhí)行空扎,regular_data方法為用例內(nèi)的變量狀態(tài)作用,可支持多個(gè)润讥;
        self.init_case_run(case_data['case_name'], status_code=status_code, request_data=request_data,
                           expect_data=expect_data, sql_check=sql_check)  #執(zhí)行自動(dòng)化測試

    @pytest.mark.parametrize('case_code',['case_002','case_003','case_004'])  #數(shù)據(jù)驅(qū)動(dòng):可執(zhí)行多條相同驗(yàn)證場景用例转锈,也可以調(diào)用方法CaseIdMake(case_002:case004),代表執(zhí)行2到4用例
    def test_auto_getuser_2_4(self, get_global_data,case_code):  #set_global_data和get_global_data方法是用于單次程序執(zhí)行中,存/取全局變量(用于依賴接口數(shù)據(jù)之間的臨時(shí)交互)楚殿;
        case_data = self.init_data['case_yml_data'][case_code]
        request_data = case_data['request_data']['body']
        expect_data = case_data['expect_data']['response_data']
        status_code = case_data['expect_data']['status_code']
        self.init_case_run(case_data['case_name'], status_code=status_code, request_data=request_data, expect_data=expect_data)

4.3撮慨、yaml文件用例/配置數(shù)據(jù)-->>示例:

  • 3.1)、測試用例數(shù)據(jù):
local_var:
  remarks: 案例信息模塊-更改案例狀態(tài)
  headers: &headers { "Content-Type": "application/json" }
  api_name: &api_name auto_getuser
  cache_data: &cache_data { 'path': 'user_cache/users_current.yml','key': 'users_current' }

case_001:
  case_name: 無法聯(lián)系客戶
  other_data: { 'sql_check': { 'sql_node': [ "select status from case_info where regist_no='&{regist_no}&' and is_deleted='f'" ],  'actual_node': [ 'expect_data' ] } }
  request_data: { "body": [ { "taskType": 4,"customized": false,"exchangeReasonId": 2 } ],"headers": *headers }
  expect_data: { 'status_code': 200,'response_data': [ { "status": 50 } ] }
  remarks: 無
case_002:
  case_name: 客戶不同意使用
  other_data: { 'sql_check': {} }
  request_data: { "body": [ { "taskType": 4,"customized": false,"exchangeReasonId": 3 } ],"headers": *headers }
  expect_data: { 'status_code': 200,'response_data':{"msg":"任務(wù)提交成功"} }
  remarks: 無
case_003:
  case_name: 異常場景-類型錯(cuò)誤
  other_data: { 'sql_check': {} }
  request_data: { "body": [ { "taskType": "錯(cuò)誤類型","customized": false,"exchangeReasonId": 3 } ],"headers": *headers }
  expect_data: { 'status_code': 409,'response_data':{"msg":"類型錯(cuò)誤"} }
  remarks: 無
case_004:
  case_name: 異常場景-缺少字段
  other_data: { 'sql_check': {} }
  request_data: { "body": [ { "customized": false,"exchangeReasonId": 3 } ],"headers": *headers }
  expect_data: { 'status_code': 409,'response_data':{"msg":"請求異常缺少參數(shù)"} }
  remarks: 無
  • 3.2)脆粥、測試接口配置文件:
local_var: &local_var
  url: &url /api/api/
  headers: &headers { 'Content-Type': 'application/json' }
case_mine: { 'name': '個(gè)人會(huì)話列表接口','method': 'get','headers': *headers,'url': '/api/v2/case/mine','data': { } }
case_send_sms: { 'name': '短信發(fā)送(變更手機(jī)號碼)','method': 'post','headers': *headers,'url': '/api/case/send-sms','data': { } }
auto_getuser: { 'name': '獲取案例信息', 'method': 'post', 'headers': *headers, 'url': '/api/auto/getuser', 'data': { } }
cases_all: { 'name': '會(huì)話歷史查詢接口','method': 'get','headers': *headers,'url': '/api/cases/all','data': { } }

4.4砌溺、內(nèi)部核心的InitTestData使用介紹-->>示例:

class InitTestData:
    @staticmethod
    def init_test_data(use_case_path: str, api_name: Optional[str] = None, headers: bool = True,
                       login_token: Optional[str] = 'web',pgsql_connect: Optional[Any] = None,
                       api_file: Optional[str] = None, api_host: Optional[str] = 'host_web') -> Dict[str, Any]:
        """
        初始化測試方法,做二次整合封裝處理
        :param use_case_path: 測試用例路徑变隔。
        :param api_name: API名稱规伐。
        :param headers: 是否包含頭信息。
        :param login_token: 登錄令牌數(shù)據(jù)文件名匣缘。
        :param pgsql_connect: SQL數(shù)據(jù)連接對象猖闪。
        :param api_file: API文件名。
        :param api_host: API主機(jī)名肌厨。
        :return: 包含系統(tǒng)配置培慌、正則方法對象、頭部信息夏哭、用例執(zhí)行對象检柬、用例YAML數(shù)據(jù)、本地變量竖配、獲取配置數(shù)據(jù)對象和獲取數(shù)據(jù)庫調(diào)用對象何址。
        """
        def get_auth_header(file_name='_token.txt') -> Optional[Dict[str, str]]:
            if not login_token:
                return None
            token = case_config.cache_token(file=login_token + file_name)
            return {'Authorization': f'Bearer {token}'}
        case_config = Case_Config(use_case_path)
        yml_conf_data = case_config.yml_cf_data
        system_config = yml_conf_data()
        regular_data = case_config.regular_data
        yml_data = case_config.yml_data
        case_yml_data = yml_data()
        local_var = case_yml_data['local_var']
        if pgsql_connect:
            pgsql_connect = system_config['pgsql_zds']
        auth_header = None
        # 如果headers為False即不傳頭部,login_token判斷是否需要帶token
        if headers:
            auth_header = get_auth_header()
        headers_dict = {**auth_header} if auth_header else {}
        headers_dict.update(local_var.get('headers', {}))
        headers_value = headers_dict if headers else {}
        api_name_value = api_name or local_var['api_name']
        class_case_run = CaseRun(api_name_value, pgsql_connect=pgsql_connect, headers=headers_value, api_file=api_file,
                                 api_host=system_config[api_host])
        return {
            'system_config': system_config,    #此對象用于獲取系統(tǒng)配置进胯;
            'regular_data': regular_data,    #此對象用于變量轉(zhuǎn)換用爪、替換,即&{variable}&正則方式轉(zhuǎn)換胁镐;
            'headers': headers_value,    #此對象用于滿足不同接口場景下偎血,頭部的入?yún)⑶闆r(即可默認(rèn)頭部诸衔、新增、為空等方式)
            'case_run': class_case_run.case_run,    #此對象用于發(fā)起測試動(dòng)作
            'case_yml_data': case_yml_data,    #用于測試用例數(shù)據(jù)讀取作用
            'local_var': local_var,    #單個(gè)yml用例的全局變量獲取作用
            'yml_conf_data': yml_conf_data,    #主要用于依賴接口的緩存數(shù)據(jù):存儲(chǔ)和讀取作用颇玷,以yml文件形式
            'yml_data': yml_data,    #用于其他測試場景下笨农,自行讀取指定yml數(shù)據(jù)
            'pgsql_db': class_case_run.pgsql_db    #用于滿足,額外場景需數(shù)據(jù)庫調(diào)用的操作
        }

4.5帖渠、測試報(bào)告處理邏輯-->>示例:

import requests
import time, json
import zipfile, os
from Testsuites.TestModel import Case_Config
class AllureServer(object):
    """調(diào)用allure-server服務(wù)生成報(bào)告,并返回鏈接地址"""
    def __init__(self, ip_port):
        self.result_url = 'http://' + ip_port + '/api/result'
        self.report_url = 'http://' + ip_port + '/api/report'
        self.test_result = 'Testsuites/TestData/com_data/test_result.yml'
    def zip_file(self, src_dir, zip_name='allure.zip'):
        z = zipfile.ZipFile(src_dir + zip_name, 'w', zipfile.ZIP_DEFLATED)
        for dir_path, dir_names, filenames in os.walk(src_dir):
            fpath = dir_path.replace(src_dir, '')
            fpath = fpath and fpath + os.sep or ''
            for filename in filenames:
                if filename != zip_name:
                    z.write(os.path.join(dir_path, filename), fpath + filename)
        z.close()
        return zip_name
    def api_result(self, file_name):
        file_zip = open(file_name, 'rb')
        files = {'allureResults': (file_name, file_zip, 'application/x-zip-compressed')}
        re_data = requests.post(self.result_url, files=files)
        return json.loads(re_data.text)
    def api_report(self, case_uuid, date_time=None, now_time=None):  #第一步:報(bào)告數(shù)據(jù)源整合及上傳
        date_time = date_time if date_time else time.strftime('%Y%m%d')
        now_time = now_time if now_time else str(int(time.time() * 1000))
        json_data = {"reportSpec": {"path": [date_time, now_time], "executorInfo": {"buildName": now_time}},
                     "results": [case_uuid], "deleteResults": False}
        re_data = requests.post(self.report_url, json=json_data, headers={"Content-Type": "application/json"})
        return json.loads(re_data.text)
    def auto_report(self, file_path, *time_data):  #第二步:報(bào)告生成html方式谒亦,并引出報(bào)告鏈接
        zip_name = self.zip_file(file_path)
        result_uuid = self.api_result(file_path + zip_name)['uuid']
        time.sleep(2)
        report_data = self.api_report(result_uuid, *time_data)['latest']
        Case_Config().yml_cache('report_link', report_data, no_cache=self.test_result)
        return report_data

五、日志模塊方面:

class Log(object):
    level_relations = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'crit': logging.CRITICAL}  # 日志級別關(guān)系映射
    def __init__(self, filename, level='info', when='D', backCount=3,
                 fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
        self.logger = logging.getLogger(filename)
        format_str = logging.Formatter(fmt)  # 設(shè)置日志格式
        self.logger.setLevel(self.level_relations.get(level))  # 設(shè)置日志級別
        sh = logging.StreamHandler()  # 往屏幕上輸出
        sh.setFormatter(format_str)  # 設(shè)置屏幕上顯示的格式
        th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount,
                                               encoding='utf-8')  # 往文件里寫入#指定間隔時(shí)間自動(dòng)生成文件的處理器
        # 實(shí)例化TimedRotatingFileHandler
        # interval是時(shí)間間隔空郊,backupCount是備份文件的個(gè)數(shù)份招,如果超過這個(gè)個(gè)數(shù),就會(huì)自動(dòng)刪除狞甚,when是間隔的時(shí)間單位锁摔,單位有以下幾種:
        # S 秒\M 分\H 小時(shí)\D 天\W 每星期(interval==0時(shí)代表星期一)\midnight 每天凌晨
        th.setFormatter(format_str)  # 設(shè)置文件里寫入的格式
        self.logger.addHandler(sh)  # 把對象加到logger里
        self.logger.addHandler(th)
def create_file():
    filepath = os.path.realpath(__file__)
    output_dir = os.path.abspath(os.path.join(filepath, "../../logs"))
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    log_name = '{}.log'.format(time.strftime('%Y-%m-%d'))
    filename = os.path.join(output_dir, log_name)
    return filename
filename = create_file()
log = Log(filename, level='debug')
logger = log.logger

**備注:其他細(xì)節(jié)就不摘取示例了,內(nèi)容太多哼审。主要目的是表達(dá)搭建的構(gòu)思以及看下示例谐腰,有個(gè)初步的概念;

六涩盾、內(nèi)部方法使用介紹:

  • 6.1)怔蚌、類初始化使用說明:


    類初始化說明.png
  • 6.2)、接口用例執(zhí)行功能點(diǎn)說明:


    請求環(huán)節(jié).png

    斷言環(huán)節(jié).png

七旁赊、結(jié)束收尾語:

  • 1、自動(dòng)化測試項(xiàng)目寫寫停鸵我埃花了兩個(gè)多月终畅,雖還有很多需要完善和優(yōu)化的,但都已完成原始架構(gòu)的設(shè)計(jì)竟闪;好歹算是個(gè)成品了离福。
  • 2、目前自動(dòng)化項(xiàng)目已經(jīng)在實(shí)際系統(tǒng)上使用炼蛤,整體效果還不錯(cuò)妖爷,基本上能滿足常規(guī)的回歸測試場景,提升測試效率理朋,非常nice ~
  • 3絮识、各位測試伙伴們,新手測試的第一次構(gòu)思搭建這自動(dòng)化項(xiàng)目嗽上,不妥之處望給與指點(diǎn)糾正次舌。
  • 4、還有動(dòng)動(dòng)小手兽愤,點(diǎn)點(diǎn)贊撒彼念;
  • 5挪圾、對外可看示例的網(wǎng)站地址:if True測試網(wǎng) (zhu-iftrue.cloud)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逐沙,隨后出現(xiàn)的幾起案子哲思,更是在濱河造成了極大的恐慌,老刑警劉巖吩案,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棚赔,死亡現(xiàn)場離奇詭異,居然都是意外死亡务热,警方通過查閱死者的電腦和手機(jī)忆嗜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崎岂,“玉大人捆毫,你說我怎么就攤上這事〕甯剩” “怎么了绩卤?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長江醇。 經(jīng)常有香客問我濒憋,道長,這世上最難降的妖魔是什么陶夜? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任凛驮,我火速辦了婚禮,結(jié)果婚禮上条辟,老公的妹妹穿的比我還像新娘黔夭。我一直安慰自己,他們只是感情好羽嫡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布本姥。 她就那樣靜靜地躺著,像睡著了一般杭棵。 火紅的嫁衣襯著肌膚如雪婚惫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天魂爪,我揣著相機(jī)與錄音先舷,去河邊找鬼。 笑死甫窟,一個(gè)胖子當(dāng)著我的面吹牛密浑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粗井,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尔破,長吁一口氣:“原來是場噩夢啊……” “哼街图!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起懒构,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤餐济,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后胆剧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體絮姆,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年秩霍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了篙悯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铃绒,死狀恐怖鸽照,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颠悬,我是刑警寧澤矮燎,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站赔癌,受9級特大地震影響诞外,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灾票,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一峡谊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刊苍,春花似錦靖苇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悼枢。三九已至埠忘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間馒索,已是汗流浹背莹妒。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绰上,地道東北人旨怠。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像蜈块,于是被迫代替她去往敵國和親鉴腻。 傳聞我的和親對象是個(gè)殘疾皇子迷扇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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