WebAPI

WebAPI

lesson_1——接口定义与http

一、WebAPI

1、接口定义

接口是个比较泛义上的概念,主要表示系统对外交互的部分,比如电源插座是电器和电能之间的接口,图形界面是应用软件和用户的接口,医院挂号大厅是医生和病人之间的接口

2、WebAPI

我们要学习的接口概念缩小到web系统提供的对外消息交互接口,通过发送对应的请求给服务器,服务器会返回相应的结果,因为其调用模式非常像编程语言中的API,所以web消息交互接口又叫webAPI。

3、Web服务接口

目前web服务接口都是基于http协议传递消息,通常大家所说的web服务接口、websevice 、webapi其实表示的都是一个意思。

主流的http消息体类型主要分为两种:

SOAP UI:使用XML格式传输消息体,架构优点就是清晰没有歧义,用软件工具就可以定义出一个接口,甚至自动生成代码,但由于阅读性差,同等信息量传输耗费的资源多。

REST: 定义了一种消息传输的风格,规定了消息体和URL的请求格式,同时也规定服务端的开发设计架构。本质上还是用http协议传输消息,至于这种风格具体是什么,并不确定,而是说明了开发的系统符合REST定义的风格规范,很多做了很多年服务端开发的人员也不一定能说清楚,由于没有强制的要求,即使开发的服务没有完全符合REST风格规范,也可以使用,所以我们也不需要了解到底REST风格是什么样子的,可能你们有疑惑,这个也不了解那么我们怎么测试webapi呢

其实我们只要知道,不管系统处于那种设计风格,只要是基于HTTP协议,我们就能对他进行测试,只要是发送数据交互过程有来有往,我们就可以用通用的方法来测试这个系统

4、WebAPI 测试特点

通常系统内部的接口是不需要QA测试的,如果测试需要提供API文档,因为这是一个判断api是否符合需求的标准。即使要测试内部接口,多数也不是从功能的角度来测试,而是以安全的角度。对外暴露的接口必须要对其进行测试,如果提供API文档,那么测试人员需要自己准备文档,然后提交给开发进行评审。

二、HTTP协议基础

1、http与html的关系

新学习web的人经常混淆的两个名词就是HTTP和HTML,都是姓H的,都是互联网领域的名词。很容易混淆。

HTML是一种用来定义网页的文本语言,会HTML,就可以编写网页;我们访问百度得到的内容是html,大家可以打开浏览器查看源码看到HTML

HTTP是在网络上传输信息(当然也包括HTML)的协议,通常用于浏览器和服务器的通信。

两者的关系做个类比,一个好比快递的商品(HTML),一个好比运输商品的方式(http),快递盒子既可以包装手机也可以包装其他商品,什么类型的信息都是不限制的

2、http请求

(1)http请求消息组成

http请求消息包含如下内容

l 请求行(必须包含):是http请求的第一行的内容,表示操作什么资源,通过什么http协议版本去获取;

例如GET /images/logo.gif HTTP/1.1,

表示从/images目录下请求获取logo.gif这个文件。

再比如POST / HTTP/1.1

表示向这个url地址 / 提交信息。

GET、POST是请求的方法,我们后面会进一步讲解

l 请求头(必须包含)

例如Accept-Language: en,可以有好多个,比如

image.png

接下来http请求消息里面可能还有

l 空行(非必须)

l 消息体(非必须)

空行的主要目的是隔开消息头和消息体的

所以:请求行和请求头是必须有的,空行和消息体可能有也可能没有。

一个GET请求消息的例子如下

GET /index.html HTTP/1.1

Host: www.example.com

这个例子里面包括请求行和一个请求头,没有消息体。

一个POST请求消息的例子如下(注意请求头和消息体之间的空行)

POST / HTTP/1.1

Host: foo.com

Content-Type: application/x-www-form-urlencoded

Content-Length: 13

say=Hi&to=Mom

(2)请求行

请求行在第一行,包含

l 请求的方法:表示请求的动作是获取信息、还是提交信息,还是修改信息、还是

删除信息等。

常见的有 GET,POST,PUT,DELETE

l GET:请求获取Request-URI所标识的资源,就是向URI指定的资源发出“获取”

请求。使用GET方法一般用在读取数据,这应该是一种最常见的请求了。

l POST:表示在Request-URI所标识的资源后附加新的数据,向指定资源提交数

据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求消息中。这个请求可能会创建新的资源或修改现有资源。

l PUT:向指定资源位置上传其最新内容。通常用于更新修改部分资源信息。

l DELETE:请求服务器删除Request-URI所标识的资源。

(3)请求头字段(request headers)

http请求头用来表示请求的其它信息,格式是:大小写不敏感的名字+冒号+空格+值,

就像键值对,一个名字一个值,很多标准的请求头字段是由国际标准化组织制定的

https://tools.ietf.org/html/rfc4229

常见的请求头字段

Accept-Encoding:

例: Accept-Encoding:gzip, deflate, br

表示客户端所支持的解码(解压缩)格式。

网络数据的传输都是占据带宽的,而将文件数据压缩能够降低数据量,减少传输时间。所以服务器在返回数据给客户端时,常常对数据进行压缩(对用户透明,通常由服务器或代理来做),而压缩的方式有多种,到底采用哪一种则需要看客户端支持哪种解码方式,这时候就可以根据header中Accept-Encoding的值。

文件或数据的压缩,由服务器或代理来做,一般不需要程序员干预;客户端接收到数据时解压缩,通常由浏览器自动完成,对用户透明。对于我们主动发起的ajax请求,一般数据量较少,不需要设置该字段。

Accept-Language:

例 : Accept-Language:zh-CN,zh;q=0.9

表示客户端支持的语言格式(不是编码格式),如中文/英文,通常浏览器直接发起请求时,浏览器会根据被设置的语言环境(默认语言),来附加上该字段。

一般我们服务器解析报文时,是不理会该字段的。他的使用场景可以是这样的,假如有个文件,有各种语言的版本,这样当不同请求发来时,我们可以根据Accept-Language的值来判断到底返回哪种语言版本给客户端。(其实这种应用场景也一般不采用判断Accept-Language字段的方法,不靠谱,还不如直接在url中体现语言版本呢)

Accept-Charset:

例: Accept-Charset:gbk,utf-8;q=0.8

表示客户端支持编码格式。服务器在返回报文时,需要将字符按照一定的编码格式转换为字节序列发送给客户端,那么该采用哪种编码格式呢? 当然作为服务器端,他可以采用任何一种编码方式,客户端都得完完整整的接收响应报文。因为目前客户端几乎都支持常见编码类型,所以服务器在返回数据时,只需要按照既定的编码方式编码,然后在响应报文中告知客户端所使用的编码方式。这样客户端在接收到报文后按照该方式进行解码,就就不会出现乱码问题。

但是,如果客户端已经定了就使用某种解码方式,那么这时候服务器端就不能那么任性了,他就需要解析Accept-Charset字段,根据这个值,来设定采用的编码方式。 如上例中,以逗号分隔,客户端支持两种编码方式,gbk和utf-8(gbk优先级高于utf8),其中utf-8后的q值,表示utf-8占的“权重”。

User-Agent:

例: User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36

表示客户端的软件环境。如上可以看出使用的是Window10 64位操作系统,Chrome浏览器等信息。服务器可以根据该字段评估客户端的环境从而给出不同的响应。(比如根据请求是从手机端或是电脑端发起的,返回不同版本的页面)

Host:

例: Host:localhost:8080

表示请求者的主机地址(IP地址)和端口号。服务器端可以根据该字段进行ip过滤等操作。

Content-Type

例子:Content-Type :application/x-www-form-urlencoded

表示请求体的消息类型 (用于POST和PUT请求中),常见的有x-www-form-urlencoded格式json格式还有xml格式

(4)请求消息体

很多时候,http请求是需要请求消息体的,比如POST、PUT等请求,但GET通常是不需要消息体的。post、put提交信息,会把消息内容翻到请求体里面,请求消息体中保存了要提交给服务端的数据信息。

常见的消息体格式就三种,json,xml,www- form-urlencoded

其中最常见的就是json是后面课程的重点,json用来传递稍微复杂的数据,传递简

单的用 www-form-urlencode。

3、http响应

(1)http响应消息组成

HTTP响应消息包含如下内容

l 状态行

l 响应头

l 空行

l 消息体

状态行和响应头是必须有的,空行和消息体可能有也可能没有。

(2)状态行

状态行在第一行,包含协议版本、状态码和描述状态的短语,比如

HTTP/1.1 200 OK

我们重点来看一下状态码,它表示客户端的的请求结果如何

· 1xx消息——请求已被服务器接收,继续处理(这类不常见,先忽略)

· 2xx成功——请求已成功被服务器接收、理解、并处理(最常见)

· 3xx重定向——需要后续操作才能完成这一请求,如地址改变(在webAPI 不常见, 就像店面移到新的地址,会贴一个告示,小王理发店已经移到xxx)

301 Moved Permanently:永久性移动到新地址,再访问这个资源,直接用新地址;

302 Moved Temporarily:这样的重定向是临时的,客户端应当继续向原有地址发

送以后的请求

304 Not Modified:表示资源未被修改,因为请求头指定的版本If-Modified-Since

或If-None-Match。在这种情况下,由于客户端仍然具有以前下

载的副本,因此不需要重新传输资源。现代的浏览器经常缓存资

源数据,如果资源未被修改,通常不需要服务端传回全部的资源

信息,节省效率和带宽。

其它的 30x不是特别常见,感兴趣的可以自己去学习。

转发与重定向区别:https://www.cnblogs.com/ChrisMurphy/p/5059940.html

· 4xx请求错误——这类的状态码代表了客户端看起来可能发生了错误,妨碍了服务器的处理。

400 Bad Request

401 Unauthorized未认证

403 Forbidden认证了,但是没有相应的权限

404 Not Found,访问http://ci.ytesting.com/portal/home33.html

常见错误,大家访问博客的时候,有时候遇到帖子被删了,或者文章的url发生了变化,用原来的地址访问就会出现这种情况

· 5xx服务器错误——服务器在处理某个正确请求时发生错误

表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生,也有可能是服务器意识到以当前的软硬件资源无法完成对请求的处理。

500 Internal Server Error服务端的代码错误导致,通常是一个异常抛出了

503 Service Unavailable由于临时的服务器维护或者过载(比如被攻击导致服务端的防御措施),服务器当前无法处理请求。这个状况是暂时的,并且将在一段时间以后恢复。

(3)响应头字段(response headers)

http的响应头用来表示响应的其它信息。

格式和请求头类似,有很多可以共用的字段。

Content-Type:text/html;charset=utf-8 消息体的数据格式,这个是经常使用的,

Content-Length 表示请求消息体的长度

Date:Sun, 30 Jul 2017 08:25:37 GMT 表示响应消息发送的日期时间

其他常用的响应头感兴趣的可以参考网上的信息,后面的课程会一一说到

(4)响应消息体

很多时候,http响应是需要消息体的。比如请求一个网页的内容,那么我们网页的html内容就在响应的消息体中给出。

lesson_2——fiddler抓包工具

一、安装下载

百度搜索 fiddler,https://www.telerik.com/fiddler

image.png

image.png

二、工作原理

fiddler抓包原理是作为代理监听了浏览器到web服务器之间的http通信请求,如图:


image.png

上面浏览器和服务器之间没有代理,抓不到包;下面浏览器和服务器之间加了代理,可以抓包

三、工具使用

安装后打开,界面如下:左边是抓到的http请求和响应,右边可以显示其具体信息。

image.png

fiddler作为代理的ip和端口是:tools-> options

image.png

动的时候,会修改系统代理设置,把自己设置为系统代理

image.png

如果我们要使用fiddler抓 web网站前后端的包,就要保证浏览器会使用 fiddler作为 proxy,有的浏览器比如火狐,不一定使用系统代理,可以手动设置浏览器代理为Fiddler的代理

这个inspector是查看内容,可以选择看raw部分

image.png

设置过滤条件

如果这样,很多包都抓了,有些包不是我们关心的。我们可以设置过滤条件,

根据host过滤:locahost; httpbin.com

image.png

根据url路径过滤

image.png

四、http抓包

1、客户端是浏览器

客户端如果是浏览器,我们通常可以利用浏览器本身的开发者工具来抓包,如果使用fidder,我们需要指定浏览器的代理。

以谷歌浏览器为例,进入设置--》高级,点击下面按钮

image.png

image.png

进入高级,确保下面中没有禁止抓 localhost,127.0.0.1 开头的包,就是空白即可

image.png

浏览器访问 网址http://localhost/mgr/login/login.html,发现可以抓包了。

2、客户端是手机

需要在手机上设置代理。

以note2 为例,演示如何长按链接的WiFi时,设置代理,要注意wifi 和运行postman的机器必须是同一个网络环境

image.png

填入fiddler所在机器的 IP地址

image.png

苹果手机的代理设置也是大同小异,大家可以网上搜索

我们还需要设置一下fidder,允许远程机器连接自己

image.png

上面都配置好了以后,我们就可以访问网站,查看抓包结果了。手机浏览网页的时候,我们可以打开手机浏览器,地址栏输入http://192.168.0.102/mgr/login/login.html

lesson_3——Postman工具

一、概述

这节课,我们和大家一起学习Postman工具

上节课说过 Web 服务接口,现在用的最多的就是 REST API接口了。所以我们主要就是讲解对 REST API 服务接口的测试。现在很多公司的系统虽然号称是REST API, 但是其实并不完全遵循这个规则。

这些系统的API其实就是 基于HTTP 的增查改删操作,分别使用了HTTP的POST、GET、UPDATE、DELETE方法的请求。所以我们后面API 接口的测试,所需要的基础知识,就是HTTP协议,就够了。

二、安装

Postman的Window应用程序安装包,可以到Postman官方网站下载。

https://www.getpostman.com/apps 打开该网站

image.png

点击选择相应操作系统上的安装包后,就可以下载了。下载下来就是一个安装包,大家双击安装即可。安装好后,会在桌面上生成相应的图标,双击即可打开。

三、使用

Postman提供了一个多窗口和多标签界面。浏览界面很简单。

l 左边的sidebar侧边栏可让您 查找和管理 请求和集合。

侧边栏分为两个主要标签,History 和 Collections。

我们在builder中,直接去填写的请求,自动保存在“History” 标签窗口中

Collection里面提供了功能,可以把我们的API请求分类,实现目录式管理

l 右边的Request Builder

Request Builder 窗口是我们以后要操作的核心区域。

就是我们用来填写 API 请求的具体内容,包括http请求的 url怎么填,头怎么填,体怎么填。点击发送后,查看响应信息是什么。都在这里。

所以,是我们以后要操作的核心区域。

Request Builder就是用来让我们发送基于HTTP的API请求,并查看服务端的响应内容的。

我们在url编辑框中输入百度的网址,如下所示:

image.png

然后,点击Send按钮,可以看到界面如下:

image.png

大家注意,蓝色线的上方是HTTP请求消息的内容展示,包括header、body等,

下方是响应消息的内容展示,包括body、cookie、header等。

四、构建http请求

API的测试,构造http请求是重点,Request Builder可以帮助我们快速构建HTTP请求。主要包括:URL(包括参数),Method(请求方法),Headers(请求头),消息体。Postman可以让我们方便的构建这些内容。

1、选择请求方法

Postman里面可以非常方便的选择HTTP请求的方法。我们可以根据接口定义,选择不同的HTTP请求的方法。如下图所示。

image.png

这里面给出了几乎所有的HTTP请求方法, 常用的就是 GET 、POST、 PUT 、DELETE

2、url的构建

url请求的输入非常简单:在url栏中填入即可。比如www.baidu.com,协议类型 http 可以省略不写,但如果是https 需要写上 https://www.baidu.com

很多时候,我们的url请求中是有参数的。

比如课程管理系统,列出课程管理时,浏览器会发出一个请求:

http://localhost/api/mgr/sq_mgr/?action=list_course&pagenum=1&pagesize=20

这个请求就是一个 API的调用,它是用来列出课程信息的。大家注意,这url中,

问号后面的部分action=list_course&pagenum=1&pagesize=20 ,术语叫做query string。

而这里面用&符号隔开就是一个个参数。这里包括pagenum(它的值为1) 、pagesize(它的值为20),可以有n多个参数。

我们可以把这个http请求直接拷贝到Postman Builder Req的地址栏里面,点击send,得到如下的结果:

image.png

说明我们可以用Postman直接发送该http请求给后端的web服务,web服务会给出

响应消息,返回课程信息列表。

如果我们点击Params 按钮,就可以看到Postman把url请求的参数显示在下面的表

格中,如下图所示。

image.png

大家以后也可以在表格中编辑,更加清晰一些,编辑一个,发现,上面也会自动更新

但有些字符出现在参数字符串中会有问题, 比如&、空格、百分号等等。& 因为它是分隔符,如果变量值中出现这个,就会误解为分隔符了。另外,有些其他特殊字符,比如空格、百分号、{}等也是特殊含义的,通常在value里面也不能直接写。地址栏里面不能直接写,但是我们不用担心,可以直接写在下面,因为Postman在发送的时候可以自动转换。也可以使用转义,比如空格符“ ” 对应 %20,其他字符也一样可以转义。

3、请求头的构建

很多Web API 需要请求消息中,设置特定的HTTP头部。要在HTTP头部中添加字段,可以点击Postman的Request Builder的请求编辑中 Headers标签,如下图,就可以像编辑表格字段一样添加HTTP的头部字段了。

image.png

可以一个个的添加,编辑好以后,点击send ,发送出去的http请求中就会携带这些添加的头部。

image.png

4、请求体的构建

有的时候,我们发送HTTP请求需要携带消息体。最常见的就是POST、PUT请求,有时delete也有消息体。

打开restapi-teach系统,添加课程,看看消息体的内容

image.png

postman构造http请求的时候,如果要填写消息体,就是点击下面的body

restapi-teach系统,看添加课程的抓包消息

我们在开发者工具栏的网络包标签页里面可以发现,浏览器会发出一个POST请求。这个请求也是一个REST API,用来添加一门课程的。

image.png

要注意Http头里面的Content-Type字段,这个字段非常重要,表明了消息体的内容的类型。

这里是 Content-Type为application/x-www-form-urlencoded的这种类型。这是一种非常常见的类型。Content-Type为application/x-www-form-urlencoded的这种类型的消息体,其实和前面的querystring的格式是一样的。只是把参数存放的位置从url里面移到了body里面!

根据这个消息体的格式,我们可以在Postman中构建一个类似的HTTP POST请求,填入如下URL和Body信息,点击发送:

注意:data部分,直接从浏览器里面拷贝就可以了

image.png

通常,REST API请求的消息体类型除了application/x-www-form-urlencoded外,

比较常见的还有 application/json 和application/xml,分别对应 json 格式的文本和xml格式的文本。

我们编程语言写的代码,运行的时候,内存中会有数据对象,典型的比如前面学的Python里面的 整数、字符串、字典、列表。如果我们要传递数据对象给另外的程序,比如一个字典里面的内容。

{ "name":"姚明", "weight":"120kg", "height":"225cm", "team":"Huston Rocket" }

这个member变量指向的对象,如何通过网络传输?我们必须要用字节串来表示对象,这个术语叫做序列化,序列化成什么格式呢?

以前经常用xml,比如

<member>
<
name>姚明</name>
<
weight>120kg</weight>
<
height>225cm</height>
<
team>Huston Rocket</team>
</
member>

当然也可以,但是xml用程序进行序列化的效率不高,而且人阅读不方便,后面就出现了json,它易于人阅读和编写,同时也易于机器解析和生成,迅速成为目前最常用的互联网系统直接交互数据的格式规范,包括我们python语言。

JSON 是一种 编程语言对象 的字符串表示法,是一种 数据保存和传输的 序列化格式。它的这种数据表示方式和我们python语言里面数据对象的字面量表示方式非常像。

其实就是因为js语言里面数据对象字面量表示方式和Python非常像。

最后,看看json数据的例子,注意最后一个元素后面不能有逗号,否则有的系统会解析失败,json用在数据传输中最常见的传输对象就是 js数组(类似Python中的列表)和js对象(类似Python中的字典)。

比如 js对象的一个例子

{
"name":"姚明",
"weight":"120kg",
"height":"225cm",
"team":"Huston Rocket" }

大家看这个写法, 和Python中字典对象的字面量(也就是代码直接表示的写法) 是一样的。

如果数据格式是json或者xml,这种情况下,用Request Builder来构造消息体,需要选择类型为raw,并且在右边的text下拉框中选择相应的类型,并且在body编辑框中直接输入相应格式的文本。如下图所示

image.png

选择不同的类型,postman 会自动添加对应的header,raw并不是一种http body的类型,而是postman 用来表示可以由大家直接填写消息内容的方法

五、collection和folder

Postman中,我们可以用Collection和Folder对API进行分类存放和管理。这样,Postman里面我们可以用 Collection 和folder 来结构化,目录化的保存我们创建的API。

Collection我们可以理解为顶级目录,类似理解为C盘D盘,folder就是下面的C盘D盘一个个目录。

怎么创建Collection和Folder呢?在左侧边栏中,点击Colleciton标签,随后点击创建图标,如下图所示:

image.png

随后,输入Collection的名称即可,创建好的Collection会出现在下方,如下所示

image.png

我们可以在创建好的Collection,一层层的建立Folder。只需右键点击Collection,在弹出菜单中选择 Add Folder即可,如果要在某个Folder中建立子Folder,只需右键点击那个Folder,在弹出菜单中选择 Add Folder即可。


image.png

通常一个产品的API接口测试,我们可以创建一个Collection和其对应。根据各个API的功能,分成不同的Test Suite, 对应到不同的Folder中。

六、导出和导入

如果我们要和同事共享这些测试的API,比如我们根据测试用例,用Postman 开发了这些API,那么 Postman 和这些API就是我们的测试工具。测试用例,如果是另外一个同事要执行,我们可以把这些API 导出到一个文件中,发给他,他就可以导入这些 API 就可以测试使用了。


image.png

image.png

七、创建postman账户

Postman有比较高级的同步功能,只要有账户,不管在那台机器登录,都可以使用该账户,获取其保存的API信息。


image.png

image.png

八、教学管理系统的功能和文档

下面我们通过我们的教学管理系统,来实践一下如何进行接口测试功能

我们的教学管理系统有如下功能

� 课程管理

� 老师管理

� 培训班管理

� 培训班期管理

� 课时管理

� 学生管理

有这样一个系统要接口测试,那就应该有文档描述接口。现在提供的课程管理API接口文档如下:

https://github.com/jcyrss/songqin-testdev/blob/master/webapi/doc/course_mgr.md

有了文档,接下来编写测试用例的注意点,和如何用postman工具进行手工测试,后面的课程会学习到。

lesson_4——Postman变量

一、变量

1、变量

前面课程的例子中,我们request URL 里面的主机地址一直都是用的 localhost,表示本机,因为我们的服务程序就运行在本机上。而实际的测试过程中,服务器地址往往并非是在本机上的。比如,我们在实验室里面,可能就是某个IP地址,比如192.168.0.133之类。我们在测试的时候,就需要修改,如果后来,再切换到本机上测试,还得再改回来localhost。

可以使用Postman中的变量,什么是Postman中的变量?在Postman中变量的概念和编程语言是类似的。Postman中的变量也就是一个字符串标识,用来对应一个值(在Postman中通常是一个字符串)。

如果Postman 的API中多处使用某个字符串的时候,有可能发生变化,我们可以使用变量代表它。 这样如果另外一个测试环境中该值需要修改,我们只要修改这个变量的定义一个地方就可以了。

Postman的变量主要分两种类型,一个是环境里面的变量,另一个是全局变量。

2、postman环境里面的变量

首先我们得明白Postman有个概念叫环境,对应测试环境postman支持多个测试环境,一个环境里面可以配置多个变量,这个变量通常是针对某个测试环境而言的,比如服务器地址、端口号等。

如果我们实际有多套测试环境(实验室中有多台测试服务器,作为测试环境),可以为这些不同的测试环境,设置相应的Postman环境,每个环境里面有相同名称的变量。

例如:

env1:

var1: hello

var2: 1234

var3: localhost

env2:

var1: hello2

var2: 1235

var3: 192.168.3.4

这样我们从一个环境切换到另一个环境的时候,只需要选择不同的Postman环境就可以了,我们不同环境的中API执行的时候,就会根据当前是哪个环境而决定采用什么样的变量值。

举个例子,我们如果搭建了两套服务器环境,里面各自运行了我们的教学管理系统。

一台在本机上,另一台在实验室里面,ip地址为192.168.0.100,我们就可以创建两套Postman环境,里面都设置一个名为apiserver的变量。

在Postman中创建环境如下,点击界面右上角的这个齿轮图标,选择Manage Environments


image.png

在弹出对话框中,点击Add按钮。

在如下所示的对话框中填入环境名称本机,和这个环境中所包含的变量,这里我们就填入一个变量serverip 表示API服务器的主机名或者IP地址

image.png

最后点击右下角的Add按钮,确定添加。

这样一套环境就创建了,我们如法炮制,再创建一套环境对应实验室的那台服务器,

环境名称为实验室,如下所示:

image.png

这样,我们就有了两套环境,一套叫local,另一套叫 lab

现在,我们只是创建了环境,和环境里面的变量,要想这个方案有效,我们还要到我们的Postman API 请求中,将所有请求的url中的主机名,替换为变量名apiserver。

应该像下面这样

image.png

就是将原来的localhost替换为{{server}} ,这里面用双花括号将变量名括起来,双花

括号就表示里面是一个变量。

当我们进行测试的时候,如果现在是使用本机的API服务,就在这里选择local 环境,

image.png

如果是使用的实验室的那台机器,就选择server1环境。Postman会使用不同环境里面的变量对应的值。

3、postman全局里面的变量

有的时候,有些变量,我们可能并不需要配置到很多套环境里面,因为基本上这就是一个固定的值,要修改的话,所有的环境里面都要修改。

比如,我们要添加的课程信息

{"name":"初中化学","desc":"初中化学课程","display_idx":"4"}

这个信息,对于不同版本系统(比如广州版本和北京版本,上海版本)的测试,值会有变化

image.png

但是和测试环境没有关系,不管是实验室机器,还是我们自己的本机环境,需要的值都是一样的,并不会变化,这时候,我们没有必要创建变量到环境中。

这时候,我们可以创建一个全局变量。全局变量和我们选择的环境无关。

点击界面右上角的这个齿轮图标,选择Manage Environments

image.png

在弹出对话框中,点击Globals按钮,如下所示。
image.png

然后加入全局变量名 course_add 其内容如下所示
image.png

别忘了勾选,不然不会使用这个全局变量

image.png

然后,我们再打开添加课程的API请求,修改其内容如下


image.png

这样,就将data参数的值用变量取代了。

二、http抓包

客户端如果是浏览器,我们通常可以利用浏览器本身的开发者工具来抓包,

如果使用fidder ,我们需要指定浏览器的代理。

以谷歌浏览器为例,进入设置--》高级,点击下面按钮


image.png

image.png

进入高级,确保下面中没有禁止抓 localhost,127.0.0.1 开头的包,就是空白即可

image.png

浏览器访问 网址http://localhost/mgr/login/login.html

发现可以抓包了。

esson_5——cookie 和 session

做web api测试的时候,大家可能会接触到cookie 和session的概念。

cookie 是我们在访问一个网站时,通常由网站服务器返回的一种标记为cookie类型数据,要求我们存储在浏览器所在电脑上,以后每次访问本网站,浏览器都会在http请求中将该数据发送过来。

image.png

l 用户登录

用户(张三)登录的时候提供用户名密码,服务端认证通过后,这次登录后续所有的 客户端和服务端的消息交互,都称之为属于一次会话(session)中进行。

session在我们软件通信中是个非常广泛的概念。到软件通讯中,通讯双方一次交流也叫会话,这次交流中的每次消息交互都属于这次会话session。

比如用户张三某次登录后,服务端就会认为创建了一个会话session,所有的前端浏览器和后端服务器每次的http消息交互都属于这个session;那么别的用户(王二)登录呢? 那就是另一个session B,它后续的所有消息交互,都属于session B。

就像现实世界中的会话需要名字来唯一标识一样,web服务的会话也需要一个名字来标记为了标志不同的会话,服务端后面生成一个叫sessionid的东西(就是一串字符,比如1234556),标志这次登录的所有后续http消息交互

sessionid通常保存在数据库中,并且对应可以存储该sessionid的一系列信息:比如 用户名、用户权限等,类似如下:

cf434534abc234sdfsd : {userid:22, username: 'jcy', login:20170419 11:22:20 ....}

另外一次登录,就另外产生记录:

cf324abc234sdfsd : {userid:25, username: 'sdfy', login:20170419 11:22:20 ....}

并且将产生的sessionid 返回给客户端,怎么返回?通过http响应里面Set-Cookie头部,要求浏览器保存到cookie中,cookie 是浏览器用来存储对应该网站信息的一个客户端的存储空间

随后,客户端每次访问同一个网站,的http请求都会将该cookie 里面的内容放到http头部中,sessionid当然也在里面,所以服务端根据sessionid,如果到表中能够查到相应的记录,就知道了对应的是哪个用户哪次登录。

session 通常用在web应用中,就是浏览器里面的网页应用。作为一种验证用户权限的机制:因为根据sessionid 就可以知道是哪个用户,从而能够知道该用户是否有相应的权限访问相应的资源。

比如我们列出课程的API请求,通过sessionid 知道是auto用户,是管理员用户从而可以知道它的权限。可能有的同学感到奇怪,为什么我们的教管系统不需要登录也能直接使用 postman API 请求直接列出资源呢?那是因为,为了方便教学前面的基础概念,修改了服务代码,故意将sessionid的校验步骤跳过去了

如果我们修改配置文件:backend\project\settings_common.py

最后一行改为:CHECK_USER_FOR_CALL = True

用postman 再执行,就会发现

{

"redirect": "/mgr/login/login.html",

"reason": "未登录",

"retcode": 302

}

因为没有sessionid,

假如,我拷贝了浏览器里面的sessionid,放入到请求头中,再试一次,就可以了

Cookie:sessionid=4zris920v7hzpugdcuqne2j5dtlc2uqq

如果自动化系统需要你们做登录,可以用postman模拟登录请求,获取sessionid

登录接口如下:

https://github.com/jcyrss/songqin-testdev/blob/master/webapi/doc/login.md

postman这样设置

image.png

查看响应里面有cookie,postman 发送请求的响应里面有cookies,就会自动保存下来。

点击这里查看

image.png

image.png

session的目的之一就是验证用户,从而可以验证用户权限

通过sessionid 来验证用户有一个弊端: 通常存储在数据库中

这样,客户端请求来的时候,服务端需要根据sessionid来查看数据库里面的用户信息。

问题1: 服务端访问数据存储,性能上会有损耗,数据访问通常是后端服务的最大瓶颈之一,特别是数量巨大的时候。

问题2: 服务端需要存储额外信息,增加了业务的复杂性。

特别是数量巨大的时候,需要水平扩容数据库,就是增加多个数据库服务节点,业务逻辑有时候需要判断数据存在哪个数据库上

esson_6——token

一、token原理

首先,大家简单理解一下什么是token,简单说,token****是包含数据信息和验证信息的一串字节数据

而token是你们公司的业务服务器生成的,生成的token 包含了将来要查看使用的数据信息,比如用户名、用户等级等,token包含了别人无法伪造的验证数据,可以用来验证你这个token 是否是我这个业务服务生成的。这个验证信息是在生成token 的时候,通过算法产生的。

这个算法是某个hash算法,称之为HMAC算法(Hash-based Message Authentication Code),这个算法就是根据源数据产生校验码一样的数据,校验码比源数据小得多,比如iso从4到20个字节的校验码,最神奇的是,只要数据信息有一点修改,对应的验证信息都会改变。比如iso下载有一个byte错了,校验码就会不同

这种算法用在我们token方案里面,在生成验证信息的时候,输入数据信息前面加上一个密钥,如下所示:

HmacFunc(密钥 + data )= HMAC

这个data就是我们源数据,比如用户名、用户等级之类的,而最终的HMAC就是校验信息,我们可以简称校验码。

如果用户抓包,发现了token里面的用户等级是白银用户,他改为钻石用户,大家想我们服务端系统能否发现?对了,验证信息HMAC就不对了。有人说,这个HmacFunc 算法是公开的,我用这个算法写段程序算出修改等级后的HMAC,不就行了吗?他没有密钥,也无法算出我们一致的验证信息HMAC就不对了。

这样不同的公司的服务器的密钥不同,产生的验证信息就不同。

注意这个验证信息非常的小,通常只有几十个字节的长度,而且通常数据信息里面还包含了有效期,防止一个token使用的时间太长,就像身份证上面有有效期一样。

我们大体明白了token的概念后,就可以理解基于token的验证方案了,这种方案是这样的,根据图讲解


image.png

1、和前面 session方案一样,都是用户登录,提供证明信息,比如用户名、密码

2、服务器验证后,注意不是产生一个sessionid 标志这次通信,而是产生一个token,

token 里面则包含了一些关于这个用户的数据信息,比如用户id,用户名、用户等级等,具体是哪些信息,不同的产品不一样,关键看以后这个产品的服务器需要快速得到用户的哪些信息,即看系统设计者选择存储哪些信息。

当然里面还包含了验证token有效性的数据,根据我们上面说的算法产生,就是

hmac(密钥 + 数据)= 验证信息

3、服务器将 token返回给客户端。

客户端得到token后,以后每次业务请求都要带上token,通常放在http请求头Authenrization中,服务端接到请求,会验证 token是否是自己签发的,验证的方法是用同样的算法hmac(密钥 + 数据)= 验证信息,计算出来验证信息和token里面的验证信息是否一致。

二、token优势

这个token方案的优点在哪里?

最大的优点就是,不需要服务端记录状态信息。他只需要验证 token是否是自己签发的,这个验证只需要再次执行一下算法hmac(密钥 + 数据)= 验证信息。 这个算法的执行速度非常快,比 session方案从数据库里面获取信息要快的多。

如果验证通过,就可以直接使用token里面存储的数据,也避免去数据库查询信息。极大的提高了速度。

lesson_7——API接口自动化测试条件

一、自动化测试需要满足的四个条件

1、自动化用例能够完成所有的测试步骤

PostMan是否支持?

不支持,因为没有完成的用例管理系统,仅凭API和文件夹是无法对应用例的。

2、每个用例的输入数据,必须要自动填入

PostMan是否支持?

支持,PostMan 本身是JS开发的一个工具,内部包含一个JS解释器可以运行JS代码

PostMan这里的解释器专业术语叫沙箱(sandbox)

特点:只能使用postman和js标准库的一些方法,不能导入外部库

使用方法:在api请求的pre-requests-scrpit标签,写JS代码,如下图


image.png

然后将此声明的变量放到请求里,用法同环境变量和全局变量,双花括号{{timestamp}}},如下图:


image.png

3、每个用例的结果检查,必须可以用代码自动完成

PostMan是否支持?

支持,在test标签,我们可以定义检查点,检查点的名称和检查点的内容我们可以自由定义,同样需要JS代码实现,方法如图:


image.png

4、具备初始化和清除功能

PostMan是否支持?

不支持,首先不具备完整的用例管理系统,没有对应的用例初始化和清除机制,其次,通常的通用的自动化框架可以引用对应编程语言的第三方库,而PostMan由于受到沙盒限制并不能任意引用第三方库。

二、附录

Postman JS代码示例:

https://learning.getpostman.com/docs/postman/scripts/test_examples/

lesson_8——Request使用方法快速介绍

一、安装导入

Request是第三方库,需要手动安装:pip install requests

代码中需要导入requests模块:import requests

二、构建各种http请求

1、get请求

<pre style="line-height:20.0pt;mso-line-height-rule:exactly;background:#EEEEEE">requests.get('https://api.github.com/events')</pre>

2、post请求

<pre style="line-height:20.0pt;mso-line-height-rule:exactly;background:#EEEEEE">requests.post('http://httpbin.org/post', data = {'key':'value'})</pre>

3、put请求

<pre style="line-height:20.0pt;mso-line-height-rule:exactly;background:#EEEEEE">requests.put('http://httpbin.org/put', data = {'key':'value'})</pre>

4、delete请求

<pre style="line-height:20.0pt;mso-line-height-rule:exactly;background:#EEEEEE">requests.delete('http://httpbin.org/delete')</pre>

三、构建URL参数

<pre style="margin-top:11.25pt;margin-right:0cm;margin-bottom:11.25pt;
margin-left:0cm;line-height:15.6pt;background:#EEEEEE">payload = {'key1': 'value1', 'key2': 'value2'}</pre>

<pre style="margin-top:11.25pt;margin-right:0cm;margin-bottom:11.25pt;margin-left:
0cm;line-height:15.6pt;background:#EEEEEE">requests.get("http://httpbin.org/get", params=payload)</pre>

params参数接收的是一个字典

四、构建请求头

只需要简单地传递一个 字典给 headers 参数就可以了

例如:

<pre style="background:white">h1={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',}</pre>

<pre style="background:white"> requests.get('http://localhost/api/mgr/sq_mgr/', headers=h1, params=payload)</pre>

五、定制请求体

<pre style="background:#F6F8FA">1、请求体类型:Content-Type:application/x-www-form-urlencoded</pre>

只需简单地传递一个字典给 data 参数

<pre style="background:white">payload={'action':'add_course', 'data':**'''{

      "name":"****初中化学****",

     "desc":"****初中化学课程****",

     "display_idx":"4"

 }'''** }</pre>

<pre style="background:white">resp=requests.post("http://localhost/api/mgr/sq_mgr/",data=payload)</pre>

<pre style="background:#F6F8FA">2、请求体类型:Content-Type: application/json</pre>

可以将字典直接传递给json参数

<pre style="background:white">payload2={

     **"action"** : **"add_course_json"**,

    **"data"** : {

              **"name"**:**"****初中化学****"**,

              **"desc"**:**"****初中化学课程****"**,

              **"display_idx"**:**"4"

              **}

   }</pre>

<pre style="margin-left:121.0pt;text-indent:-121.0pt;mso-char-indent-count:-11.0;
background:white">resp=requests.post("http://localhost/apijson/mgr/sq_mgr/", json=payload2)</pre>

注意参数和URL的区别!!!!

六、查看响应内容

1、先获取到响应对象response,拿到响应对象就可以查看服务器返回的各种消息内容了

<pre style="background:white">resp=requests.post("http://localhost/api/mgr/sq_mgr/", data=payload)</pre>

2、查看响应体:

<pre style="background:white">resp****.****text</pre>

3、查看响应头:

<pre style="background:white">resp****.****headers</pre>

4、如果响应体恰巧是json格式:

<pre style="background:white">resp****.****json****()</pre>

自动把json格式的字符串转成python对象,通常都是字典类型,那么再获取字典里面具体的值就很好操作了

<pre style="text-indent:22.0pt;mso-char-indent-count:2.0;background:white">retObj=resp.json()</pre>

<pre style="text-indent:22.1pt;mso-char-indent-count:2.0;background:white">if retObj['retcode'] == 0:

 print(**'pass'**) **else**:

 print(retObj[**'retcode'**])</pre>

七、附录

Postman JS代码示例:

https://learning.getpostman.com/docs/postman/scripts/test_examples/

request中文官方文档:

http://docs.python-requests.org/zh_CN/latest/

lesson_9——接口认证

一、服务器的接口访问

服务器的接口并不是所有用户都可以随意访问的,需要通过一个验证机制,这个验证机制可以是session或token。

以session为例,Session存储于服务器,通常用户登录后,服务器会把配套的sessionid发送给客户端,客户端在下次请求服务器的时候会带上这个sessionid,相当于拿到了入场券,服务器会验证这个入场券是不是有效的,验证通过后才会允许客户端访问对应的接口内容,之后客户端每发送一次请求都要带上sessionid,同样服务器每次都会验证其有效性。

通常http协议中sessionid是放在cookies这个请求头里面。

二、****修改接口验证

下面以教学系统为例,修改接口验证:

1、打开教学系统配置文件:

${your_path} \restapi-teach\backend\project\settings_common.py

2、翻到最后一行修改成:

CHECK_USER_FOR_CALL = True

3、重启服务器

三、获取sessionid

1、通过登录接口获取Set-Cookie请求头

<pre style="background:white">payload_user={'username':'auto','password':'sdfsdfsdf'} resp=requests.post('http://localhost/api/mgr/loginReq',</pre>

<pre style="text-indent:121.0pt;mso-char-indent-count:11.0;background:white">data=payload_user)</pre>

<pre style="background:white">cookie=resp.headers['Set-Cookie']</pre>

字符串分割法获取sessionid:

<pre style="background:white">sessionid=cookie.split(';')[0].split('=')[1]</pre>

<pre style="background:white">2、通过登录接口返回的response对象直接获取</pre>

<pre style="background:white">payload_user={'username':'auto','password':'sdfsdfsdf'} resp=requests.post('http://localhost/api/mgr/loginReq',</pre>

<pre style="background:white"> data=payload_user)</pre>

<pre style="background:white">cookies=resp.cookies</pre>

<pre style="background:white">sessionid=cookies['sessionid']</pre>

四、代码里添加sessionid方法

1、直接添加到cookies请求头里

<pre style="background:white">h1={'Cookie': 'sessionid=imk4esz7jep90gcbt3oo790ex3srt5dc'} requests.get('http://localhost/api/mgr/sq_mgr/',

headers=h1,

params=payload)</pre>

2、直接告诉requests,让它帮我添加

<pre style="background:white">payload={'action':'list_course','pagenum':1,'pagesize':20} cookie={'Cookie': 'sessionid=imk4esz7jep90gcbt3oo790ex3srt5dc'} requests.get('http://localhost/api/mgr/sq_mgr/',

cookies=cookie,

params=payload)</pre>

3、登录接口包含了cookies,直接获取使用

<pre style="background:white">payload_user={'username':'auto','password':'sdfsdfsdf'} resp=requests.post('http://localhost/api/mgr/loginReq',</pre>

<pre style="background:white"> data=payload_user)</pre>

<pre style="background:white">resp=requests.get('http://localhost/api/mgr/sq_mgr/',

     cookies=resp.cookies,

     params=payload)</pre>

五、作业

https://github.com/jcyrss/songqin-testdev/blob/master/webapi/tasks/06.md

要求大家发送请求的时候带上sessionid,如何获取sessionid,要求自动登录获取
lesson_10——API接口测试数据库操作

一、测试中操作数据库场景

做测试的时候遇到很多场景,有什么样的情况会直接操作数据库?

1、用例检查点

通常我们是不建议直接查看数据库内容来检查功能的,但是在没有外部接口或者图形界面验证的情况下呢,只能通过查询数据库来进行验证。

准备用例数据:

需要大量构建测试数据的情况下,通常出现在性能测试。

比如,以在线教育系统为例,有这样一个用例,需要测试系统中已经有大量的学生,比如1千万个的情况下。调用列出学生,要求返回结果正确,并且响应速度不低于2秒钟。如果我们手工从界面上要添加1千个条数据,估计要花好几天时间。显然不能手工去做。那么有一种方法就是:直接操作数据库,把数据插入到表中。

二、应用程序访问数据库原理

image.png

1、流程

我们应用程序不是直接操作数据库磁盘里的数据,而是通过网络请求访问数据库服务进程,服务进程根据发送过来的命令再操作数据库。而且通常我们的应用程序和数据库服务都不在一台机器上,所以需要通过网络请求来访问。此网络协议偏向底层,属于数据库厂商自定义的一个标准,每个牌子的数据库都不一样。

2、服务端

数据库服务进程:负责网络通信,传递数据信息,将数据库访问命令传递给DB

DB:对应具体的数据库,通常表现形式为磁盘上的数据库文件,内部存储数据库信息

3、客户端

APP:访问数据库的应用程序,比如(heidisql),或者访问数据的程序代码。

Driver:负责联通APP与数据库服务之间的联通,相当于沟通桥梁。

lesson_11——python代码操作数据库

一、安装驱动mysql-client

cmd命令窗口直接安装:保证自己python环境是3.6

pip install mysqlclient==1.3.12

二、连接数据库

1、创建一个连接对象

<pre style="background:white">connection = MySQLdb.connect(</pre>

<pre style="background:white"> host=host, user=user, passwd=passwd, db=dbname, charset = "utf8"</pre>

<pre style="background:white">)</pre>

2、获取一个游标对象

返回一个cursor对象,中文叫游标 c = connection.cursor()

三、查询数据库内容

1****、通过游标来执行SQL语句

<pre style="margin-left:10.9pt;mso-para-margin-left:1.04gd;background:white"># 执行一个获取 sq_course 表中所有记录的 sql 语句 sql = "SELECT ***** FROM sq_course" c.execute(sql)</pre>

2、取出查询到的数据可以用以下方法:

fenchall —— 查询所有数据,返回查询到所有数据

<pre style="text-indent:12.0pt;mso-char-indent-count:1.0;background:white">res = c.fetchall() </pre>

<pre style="text-indent:11.0pt;mso-char-indent-count:1.0;background:white">returns:</pre>

<pre style="text-indent:11.0pt;mso-char-indent-count:1.0;background:white">((1, 'Python', 'Python基础与实战', 1),</pre>

<pre style="background:white"> (2, 'Selenium', 'web自动化', 2),</pre>

<pre style="background:white"> (5, 'Python2018-12-19 15:18:05', 'python课程', 8),</pre>

<pre style="background:white"> (6, 'Python22018-12-19 15:18:22', 'python课程2', 5))</pre>

<pre style="text-indent:12.05pt;mso-char-indent-count:1.0;background:white">Tips:****当查询的表格数据非常多达到十万级别时不建议使用此方法</pre>

fenchone —— 查询一条数据,返回一行数据

<pre style="text-indent:11.0pt;mso-char-indent-count:1.0;background:white">row = c.fetchone()</pre>

<pre style="text-indent:11.0pt;mso-char-indent-count:1.0;background:white">returns:</pre>

<pre style="text-indent:11.0pt;mso-char-indent-count:1.0;background:white">(1, 'Python', 'Python基础与实战', 1)</pre>

Fenchmany —— 查询多条,返回列表,参数是查询多少条

data = c.fetchmany(2)

returns:

((1, 'Python', 'Python基础与实战', 1), (2, 'Selenium', 'web自动化', 2))

四、插入数据

构造插入数据的SQL语句,同时用游标执行

sql= "INSERT INTO sq_course (name,desc,display_idx)

VALUES ('****语文****','****语文课****',9)"

c.execute(sql)

所有对数据库修改的动作,必须要commit才会生效 connection.commit()

五、修改数据

python代码部分同插入数据

更改SQL语句:

"UPDATE sq_course SET name='****初中化学****' WHERE name='****数学****' "

即如下:

sql= "UPDATE sq_course SET name='****初中化学****' WHERE name='****数学****' "

c.execute(sql)

所有对数据库修改的动作,必须要commit才会生效 connection.commit()

六、删除数据

python代码部分同插入数据

更改SQL语句:

sql= "DELETE FROM sq_course WHERE name='****语文****' "

即如下:

sql= "UPDATE sq_course SET name='****初中化学****' WHERE name='****数学****' "

c.execute(sql)

所有对数据库修改的动作,必须要commit才会生效 connection.commit()

七、示例——修改教管系统数据库连接

打开教管系统配置文件:\restapi-teach\backend\project\settings.py,

修改数据库配置,将原先sqlite的配置注释掉,改成mysql的配置,注意用户名和密码填写正确。


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

推荐阅读更多精彩内容