Flask后端笔记(六)单元测试、部署

单元测试

为什么要测试?

Web程序开发过程一般包括以下几个阶段:[需求分析,设计阶段,实现阶段,测试阶段]。其中测试阶段通过人工或自动来运行测试某个系统的功能。目的是检验其是否满足需求,并得出特定的结果,以达到弄清楚预期结果和实际结果之间的差别的最终目的。

测试的分类:

测试从软件开发过程可以分为:单元测试、集成测试、系统测试等。在众多的测试中,与程序开发人员最密切的就是单元测试,因为单元测试是由开发人员进行的,而其他测试都由专业的测试人员来完成。所以我们主要学习单元测试。

什么是单元测试?

程序开发过程中,写代码是为了实现需求。当我们的代码通过了编译,只是说明它的语法正确,功能能否实现则不能保证。 因此,当我们的某些功能代码完成后,为了检验其是否满足程序的需求。可以通过编写测试代码,模拟程序运行的过程,检验功能代码是否符合预期。

单元测试就是开发者编写一小段代码,检验目标代码的功能是否符合预期。通常情况下,单元测试主要面向一些功能单一的模块进行。

举个例子:一部手机有许多零部件组成,在正式组装一部手机前,手机内部的各个零部件,CPU、内存、电池、摄像头等,都要进行测试,这就是单元测试。

在Web开发过程中,单元测试实际上就是一些“断言”(assert)代码。

断言就是判断一个函数或对象的一个方法所产生的结果是否符合你期望的那个结果。 python中assert断言是声明布尔值为真的判定,如果表达式为假会发生异常。单元测试中,一般使用assert来断言结果。

断言方法的使用:
在这里插入图片描述
断言语句类似于:

if not expression:
    raise AssertionError

常用的断言方法:

assertEqual     如果两个值相等,则pass
assertNotEqual  如果两个值不相等,则pass
assertTrue      判断bool值为True,则pass
assertFalse     判断bool值为False,则pass
assertIsNone    不存在,则pass
assertIsNotNone 存在,则pass

单元测试的基本写法:

首先,定义一个类,继承自unittest.TestCase

import unittest
class TestClass(unitest.TestCase):
    pass

其次,在测试类中,定义两个测试方法

import unittest
class TestClass(unittest.TestCase):

    #该方法会首先执行,方法名为固定写法
    def setUp(self):
        pass

    #该方法会在测试代码执行完后执行,方法名为固定写法
    def tearDown(self):
        pass

最后,在测试类中,编写测试代码

import unittest
class TestClass(unittest.TestCase):

    #该方法会首先执行,相当于做测试前的准备工作
    def setUp(self):
        pass

    #该方法会在测试代码执行完后执行,相当于做测试后的扫尾工作
    def tearDown(self):
        pass
    #测试代码
    def test_app_exists(self):
        pass

login.py

# coding:utf-8

from flask import Flask, request, jsonify


app = Flask(__name__)


@app.route("/login", methods=["POST"])
def login():
    """登录"""
    name = request.form.get("name")
    password = request.form.get("password")

    # ""  0  [] () {} None 在逻辑判断时都是假
    if not all([name, password]):
        # 表示name或password中有一个为空或者都为空
        return jsonify(code=1, message=u"参数不完整")

    if name == "admin" and password =="python":
        return jsonify(code=0, message=u"OK")
    else:
        return jsonify(code=2, message=u"用户名或密码错误")


if __name__ == '__main__':
    app.run()


test_login.py

# coding:utf-8

import unittest
from login import app
import json


class TestLogin(unittest.TestCase):
    """定义测试案例"""
    def setUp(self):
        """在执行具体的测试方法前,先被调用"""
        # 可以使用python的http标准客户端进行测试
        # urllib  urllib2  requests

        # 使用flask提供的测试客户端进行测试
        self.client = app.test_client()

    def test_empty_name_password(self):
        """测试模拟场景,用户名或密码不完整"""
        # 使用客户端向后端发送post请求, data指明发送的数据,会返回一个响应对象
        response = self.client.post("/login", data={
    
    })

        # respoonse.data是响应体数据
        resp_json = response.data

        # 按照json解析
        resp_dict = json.loads(resp_json)

        # 使用断言进行验证
        self.assertIn("code", resp_dict)

        code = resp_dict.get("code")
        self.assertEqual(code, 1)

        # 测试只传name
        response = self.client.post("/login", data={
    
    "name": "admin"})

        # respoonse.data是响应体数据
        resp_json = response.data

        # 按照json解析
        resp_dict = json.loads(resp_json)

        # 使用断言进行验证
        self.assertIn("code", resp_dict)

        code = resp_dict.get("code")
        self.assertEqual(code, 1)

    def test_wrong_name_password(self):
        """测试用户名或密码错误"""
        # 使用客户端向后端发送post请求, data指明发送的数据,会返回一个响应对象
        response = self.client.post("/login", data={
    
    "name": "admin", "password": "itcast"})

        # respoonse.data是响应体数据
        resp_json = response.data

        # 按照json解析
        resp_dict = json.loads(resp_json)

        # 使用断言进行验证
        self.assertIn("code", resp_dict)

        code = resp_dict.get("code")
        self.assertEqual(code, 2)


if __name__ == '__main__':
    unittest.main()

测试数据库

# coding:utf-8


import unittest
from author_book import Author, db, app


class TestDatabase(unittest.TestCase):
    """测试数据库的案例"""
    def setUp(self):
        app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:[email protected]:3306/flask_test"
        db.drop_all()
        db.create_all()

    def test_author(self):
        """测试添加作者的案例"""
        author = Author(name="itcast", email="[email protected]")
        db.session.add(author)
        db.session.commit()

        ret_author = Author.query.filter_by(name="itcast").first()

        self.assertIsNotNone(ret_author)

        self.assertEqual(ret_author.name, "itcast")

    def tearDown(self):
        """在所有测试方法执行后,被调用"""
        # 清除记录的测试任务
        db.session.remove()
        # 清除数据库数据
        db.drop_all()


if __name__ == '__main__':
    unittest.main()

部署

当我们执行下面的hello.py时,使用的flask自带的服务器,完成了web服务的启动。在生产环境中,flask自带的服务器,无法满足性能要求,我们这里采用Gunicorn做wsgi容器,来部署flask程序。Gunicorn(绿色独角兽)是一个Python WSGI的HTTP服务器。从Ruby的独角兽(Unicorn )项目移植。该Gunicorn服务器与各种Web框架兼容,实现非常简单,轻量级的资源消耗。Gunicorn直接用命令启动,不需要编写配置文件,相对uWSGI要容易很多。

区分几个概念:

  • WSGI:全称是Web Server Gateway Interface(web服务器网关接口),它是一种规范,它是web服务器和web应用程序之间的接口。它的作用就像是桥梁,连接在web服务器和web应用框架之间。

  • uwsgi:是一种传输协议,用于定义传输信息的类型。

  • uWSGI:是实现了uwsgi协议WSGI的web服务器。

我们的部署方式: nginx + gunicorn + flask

# hello.py

from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
    return '<h1>hello world</h1>'

if __name__ == '__main__':
    app.run(debug=True)

使用Gunicorn:

web开发中,部署方式大致类似。简单来说,前端代理使用Nginx主要是为了实现分流、转发、负载均衡,以及分担服务器的压力。Nginx部署简单,内存消耗少,成本低。Nginx既可以做正向代理,也可以做反向代理。

正向代理:请求经过代理服务器从局域网发出,然后到达互联网上的服务器。

特点:服务端并不知道真正的客户端是谁。

反向代理:请求从互联网发出,先进入代理服务器,再转发给局域网内的服务器。

特点:客户端并不知道真正的服务端是谁。

区别:正向代理的对象是客户端。反向代理的对象是服务端。

安装gunicorn

pip install gunicorn
查看命令行选项: 安装gunicorn成功后,通过命令行的方式可以查看gunicorn的使用信息。

$gunicorn -h
直接运行:

#直接运行,默认启动的127.0.0.1::8000
gunicorn 运行文件名称:Flask程序实例名

指定进程和端口号:
-w: 表示进程(worker)。 -b:表示绑定ip地址和端口号(bind)。-D:后台运行。

$gunicorn -w 4 -b 127.0.0.1:5001 -D 运行文件名称:Flask程序实例名

在这里插入图片描述

安装Nginx

$ sudo apt-get install nginx

Nginx配置:

默认安装到/usr/local/nginx/目录,进入目录。

启动nginx:

#启动
sudo sbin/nginx
#查看
ps aux | grep nginx
#停止
sudo sbin/nginx -s stop

打开/usr/local/nginx/conf/nginx.conf文件

server {
    # 监听80端口
    listen 80;
    # 本机
    server_name localhost; 
    # 默认请求的url
    location / {
        #请求转发到gunicorn服务器
        proxy_pass http://127.0.0.1:5001; 
        #设置请求头,并将头信息传递给服务器端 
        proxy_set_header Host $host; 
    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_27251475/article/details/121026460
今日推荐