一、单元测试
1.1为什么要测试?
Web程序开发过程一般包括以下几个阶段:[需求分析,设计阶段,实现阶段,测试阶段]。其中测试阶段通过人工或自动来运行测试某个系统的功能。目的是检验其是否满足需求,并得出特定的结果,以达到弄清楚预期结果和实际结果之间的差别的最终目的。
1.2测试的分类:
测试从软件开发过程可以分为:单元测试、集成测试、系统测试等。在众多的测试中,与程序开发人员最密切的就是单元测试,因为单元测试是由开发人员进行的,而其他测试都由专业的测试人员来完成。所以我们主要学习单元测试。
1.3什么是单元测试?
程序开发过程中,写代码是为了实现需求。当我们的代码通过了编译,只是说明它的语法正确,功能能否实现则不能保证。 因此,当我们的某些功能代码完成后,为了检验其是否满足程序的需求。可以通过编写测试代码,模拟程序运行的过程,检验功能代码是否符合预期。
单元测试就是开发者编写一小段代码,检验目标代码的功能是否符合预期。通常情况下,单元测试主要面向一些功能单一的模块进行。
举个例子:一部手机有许多零部件组成,在正式组装一部手机前,手机内部的各个零部件,CPU、内存、电池、摄像头等,都要进行测试,这就是单元测试。
在Web开发过程中,单元测试实际上就是一些“断言”(assert)代码。
断言就是判断一个函数或对象的一个方法所产生的结果是否符合你期望的那个结果。 python中assert断言是声明布尔值为真的判定,如果表达式为假会发生异常。单元测试中,一般使用assert来断言结果。
1.4断言方法的使用:
断言语句类似于:
if not expression: raise AssertionError
常用的断言方法:
assertEqual 如果两个值相等,则pass assertNotEqual 如果两个值不相等,则pass assertTrue 判断bool值为True,则pass assertFalse 判断bool值为False,则pass assertIsNone 不存在,则pass assertIsNotNone 存在,则pass
1.5如何测试?
简单的测试用例:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,
def fibo(x): if x == 0: resp = 0 elif x == 1: resp = 1 else: return fibo(x-1) + fibo(x-2) return resp assert fibo(5) == 5
1.6单元测试的基本写法:
首先,定义一个类,继承自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工作在测试模式下 #app.config[“TESTING”] = Ture app.testing = Ture # 使用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()
1.7发送邮件测试:
#coding=utf-8 import unittest from Flask_day04 import app class TestCase(unittest.TestCase): # 创建测试环境,在测试代码执行前执行 def setUp(self): self.app = app # 激活测试标志 app.config['TESTING'] = True self.client = self.app.test_client() # 在测试代码执行完成后执行 def tearDown(self): pass # 测试代码 def test_email(self): resp = self.client.get('/') print resp.data self.assertEqual(resp.data,'Sent Succeed')
1.8数据库测试:
#coding=utf-8 import unittest from author_book import * #自定义测试类,setUp方法和tearDown方法会分别在测试前后执行。以test_开头的函数就是具体的测试代码。 class DatabaseTest(unittest.TestCase): def setUp(self): app.config['TESTING'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@localhost/test0' self.app = app db.create_all() def tearDown(self): db.session.remove() db.drop_all() #测试代码 def test_append_data(self): au = Author(name='itcast') bk = Book(info='python') db.session.add_all([au,bk]) db.session.commit() author = Author.query.filter_by(name='itcast').first() book = Book.query.filter_by(info='python').first() #断言数据存在 self.assertIsNotNone(author) self.assertIsNotNone(book)
二、部署
当我们执行下面的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)
2.1使用Gunicorn:
web开发中,部署方式大致类似。简单来说,前端代理使用Nginx主要是为了实现分流、转发、负载均衡,以及分担服务器的压力。Nginx部署简单,内存消耗少,成本低。Nginx既可以做正向代理,也可以做反向代理。
正向代理:请求经过代理服务器从局域网发出,然后到达互联网上的服务器。
特点:服务端并不知道真正的客户端是谁。
反向代理:请求从互联网发出,先进入代理服务器,再转发给局域网内的服务器。
特点:客户端并不知道真正的服务端是谁。
区别:正向代理的对象是客户端。反向代理的对象是服务端。
2.2安装gunicorn
pip install gunicorn
查看命令行选项: 安装gunicorn成功后,通过命令行的方式可以查看gunicorn的使用信息。
$gunicorn -h
直接运行:
#直接运行,默认启动的127.0.0.1::8000 gunicorn 运行文件名称:Flask程序实例名
指定进程和端口号: -w: 表示进程(worker)。 -b:表示绑定ip地址和端口号(bind)。
$gunicorn -w 4 -b 127.0.0.1:5001 运行文件名称:Flask程序实例名
$gunicorn -w 4 -b 127.0.0.1:5001 --access-logfile ./logs/log (访问历史属性记录)运行文件名称:Flask程序实例名
2.3安装Nginx
$ sudo apt-get install nginx
2.4Nginx配置:
默认安装到/usr/local/nginx/目录,进入目录。
启动nginx:
#启动 sudo sbin/nginx #查看 ps aux | grep nginx #停止 sudo sbin/nginx -s stop
打开/usr/local/nginx/conf/nginx.conf文件