1.请求分析
首先我们分析一下用户从界面上完成一笔销售出库需要进行的几个主要步骤。
(1) 使用正确的用户名和密码登录成功。
(2) 点击销售出库的链接。
(3) 输入商品条码,点击“确认”。
(4) 输入会员电话,点击“查询会员信息”。
(5) 点击“确认收款”,操作完成。

那么从协议层面上是不是也是对应的这些请求呢?登录请求前面已经实现过,自不必说。点击“销售出库”链接也很容易知道是一个普通的GET请求,暂且跳过。这里我们主要通过工具从输入商品条码开始捕获分析。
(1)输入商品条码是一个POST请求,分别列出请求和响应的重要信息,请求的正文是商品条码,请求的正文则将商品的日期、货号、名称、条码、单价以JSON数据格式返回。
POST http://localhost:8080/WoniuSales/sell/barcode HTTP/1.1 ... barcode=1001 |
HTTP/1.1 200 OK ... [{"createtime":"<option value='60'>尺码:60,剩余:80件</option><option value='70'>尺码:70,剩余:80件</option><option value='80'>尺码:80, 剩余:80件</option>##2.0##78","goodsserial":"M8Q9066C","goodsname":"人字呢背心裙","barcode":"1001","unitprice":239.0}] |
(2)接下来输入会员电话,请求正文为电话号码,响应正文为该会员的详细信息,仍然以JSON格式返回。
POST http://localhost:8080/WoniuSales/customer/query HTTP/1.1 ... customerphone=186836668866 |
HTTP/1.1 200 OK ... [{"childsex":"女", "childdate":"2015-12-31","creditcloth":2136,"creditkids":500, "createtime":"2017-10-01 15:40:06","customerphone":"186836668866", "customerid":1, "credittotal":2636, "customername":"某某","updatetime":"2018-01-05 20:39:07", "userid":1}] |
这两个请求目前看起来没有太多的关联,我们继续往下分析。
(3) 点击确认收款,预想中应该是发送一个POST请求,但通过工具的捕获发现实际上是两个请求。
第一个请求是将会员的信息作为请求参数,得到的响应正文代表新增成功的会员支付记录编号。
POST http://localhost:8080/WoniuSales/sell/summary HTTP/1.1 ... customerphone=186836668866&paymethod=现金&totalprice=186&creditratio=2.0 &creditsum=372&tickettype=无&ticketsum=0&oldcredit=2636 |
HTTP/1.1 200 OK ... 240 |
第二个请求则将所购买商品的信息作为请求参数,响应正文“pay-success”表示付款成功。
POST http://localhost:8080/WoniuSales/sell/detail HTTP/1.1 ... sellsumid=239&barcode=1001&goodsserial=M8Q9066C&goodsname=人字呢背心裙&goodssize=60&unitprice=239&discountratio=78&discountprice=186.42&buyquantity=1&subtotal=186.42 |
HTTP/1.1 200 OK ... pay-successful |
为什么会出现这种情况?从用户界面的角度来说,只进行了一次鼠标点击的操作,从这个系统设计的角度来说,付款成功后需要对数据库中的会员支出表和销售表各插入一条数据,这样才能保证后续使用时数据的一致性。所以这里也是提醒大家,界面只是一个给用户的呈现方式,其本质是协议的之间的数据传输,我们要理解其中的区别。
2.请求关联
要完成一笔销售出库,需要向服务器发起多次请求,并且后续的请求还要依赖前面请求返回的数据,这里就涉及到请求之间的数据关联。以新增会员支付记录的请求为例,实现成功对http://localhost:8080/WoniuSales/sell/summary发送请求。从上一节的分析可知,该请求的参数有8个,通过系统设计文档理解每个参数的意义。

(1) customerphone:客户电话,与http://localhost:8080/WoniuSales/customer/query请求时的参数的值保持一致即可。
(2) paymethod:支付方式,由用户选择,这里为了方便我们固定为“现金”即可。
(3) totalprice:支付总价,需要取出http://localhost:8080/WoniuSales/sell/barcode响应中的unitprice字段,将其乘以折扣,再乘以数量计算得出。我们默认折扣都为7.8折,且数量均为1,那么totalprice的值为unitprice*0.78。
(4) creditratio:积分倍数.,设置为固定的2.0即可。
(5) creditsum:新增积分,等于unitprice乘以creditratio。
(6) tickettype:优惠券编号或原因,设置为无。
(7) ticketsum:优惠金额,设置为0。
(8) oldcredit:初识积分,来源于http://localhost:8080/WoniuSales/customer/query响应里的credittotal的值。
发送请求的8个参数中,totalprice和oldcredit是需要借助上一个请求中的响应数据来解析的。实现过程如下:
先实现一个根据编号获取商品信息的请求,提前构造好商品编码barcode,发送POST请求即可。
from locust import HttpLocust,TaskSet,task class UserBehavior(TaskSet): @task def getGoods(self): body = {'barcode':'1001'} res = self.client.post('/WoniuSales/sell/barcode',data=body) print(res.text) class WebSite(HttpLocust): task_set = UserBehavior min_wait = 1000 max_wait = 3000 |
依次启动Locust并运行测试,控制台将回输出如下的响应信息,结合响应头的里的正文类型字段“Content-Type: application/json;charset=UTF-8”可知,这是一种JSON格式的数据,需要进行特殊的处理。
[2018-06-02 20:25:06,859] Teacher-Chennan/INFO/stdout: [{"createtime":"<option v alue='60'>尺码:60,剩余:79件</option><option value='70'>尺码:70,剩余:80件</option ><option value='80'>尺码:80,剩余:80件</option>##2.0##78","goodsserial":"M8Q9066C ","goodsname":"人字呢背心裙","barcode":"1001","unitprice":239.0}] |
整合后的代码如下:
(1) 导入程序需要用到的locus模块和json模块;
(2) 在on_start方法中实现了登录的操作,以便后续操作能正常进行;
(3) 在getGoods方法中发送了一个获取商品信息的POST请求,并使用loads方法对响应的JSON数据进行解析,由于解析后的数据是一个字典与列表的嵌套,先访问此列表中的第一个元素得到字典,再通过键名得到对应的值,最后乘以折扣0.78并转换为整型,将价格返回给调用者。
(4) 实现getCustomer方法,与第三步思路完全一样。
(5) 实现task任务doStart,利用getGoods和getCustomer方法的返回值构造请求正文body并发送。
(6) 实现WebSite类。
from locust import HttpLocust,TaskSet,task import json class UserBehavior(TaskSet): # 预先登录 def on_start(self): body = {'username':'admin','password':'admin123','verifycode':'0000'} self.client.post("/WoniuSales/user/login",body) def getGoods(self): body = {'barcode':'1001'} res = self.client.post('/WoniuSales/sell/barcode',data=body) # 解析JSON数据 newData = json.loads(res.text) # 提取unitprice字段的值进行计算 totalPrice = int(newData[0]['unitprice'] * 0.78) return totalPrice
def getCustomer(self): body = {'customerphone':'186836668866'} res = self.client.post('/WoniuSales/customer/query',data=body) newData = json.loads(res.text) oldcredit = int(newData[0]['credittotal']) return oldcredit @task def doStart(self): body ={'customerphone':'186836668866','paymethod':'现金',\ 'totalprice':self.getGoods(),'creditratio':'2.0',\ 'creditsum':'372','tickettype':'无','ticketsum':'0',\ 'oldcredit':self.getCustomer()} res = self.client.post('/WoniuSales/sell/summary',data=body) print(res.text) class WebSite(HttpLocust): task_set = UserBehavior min_wait = 1000 max_wait = 3000 |
启动Locust,打开WEB端设置模拟用户数和每秒用户生成数均为1,执行测试。查看控制台,不断输出新增成功的编号,脚本实现正确。
[2018-06-02 20:45:46,142] Teacher-Chennan/INFO/stdout: 240 [2018-06-02 20:45:46,145] Teacher-Chennan/INFO/stdout:
${comment['nickname']} ${comment['createtime']}
${comment.content}
${reply.nickname} 回复 ${comment.nickname}
${reply.createtime}
回复内容:${reply.content}
|