日課書

编程100小时挑战

利用Flask创建一个mini微博(4)

利用Flask创建一个mini微博(1)
利用Flask创建一个mini微博(2)
利用Flask创建一个mini微博(3)

Step 5:测试程序

参考:
Flask/Docs/Testing Flask Application
minitwit/test

没有经过测试的程序,在后续的修改和添加功能时很难处理。如果一个程序有自动测试,你就可以放心的秀敢程序,然后通过运行测试就知道新的修改对原有功能有没有影响。

Flask提供了一个测试客户端来处理应用上下文的问题。有了它你就可以使用你自己的测试方案了。这里我们将使用Python标准库unittest模块进行测试。

测试框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# miniweibo_tests.py
import os
import miniweibo
import unittest
import tempfile

class FlaskTestCase(unittest.TestCase):

def setUp(self):
self.db_fd, miniweibo.app.config['DATABASE'] = tempfile.mkstemp()
miniweibo.app.config['TESTING'] = True
self.app = miniweibo.app.test_client()
with miniweibo.app.app_context():
miniweibo.init_db()

def tearDown(self):
os.close(self.db_fd)
os.unlink(miniweibo.app.config['DATABASE'])

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

tempfile.makstemp()随机生成一个文件,返回文件操作实例和文件名。设置TESTING变量后,程序就不会捕捉请求时发生的错误,以便最后一起输出测试结果。在测试开始初始化数据库。测试完后关闭数据库并删除临时文件。

为了测试时方便调用,在测试实例前编写一些辅助函数。比如我们要测试注册的功能,首先编写注册的辅助函数,然后在测试注册时出现的各种可能性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def register(self, username, password, password2=None, email=None):
"""Helper function to register a user"""
if password2 is None:
password2 = password
if email is None:
email = username + '@example.com'
return self.app.post('/register', data={
'username': username,
'password': password,
'password2': password2,
'email': email
}, follow_redirects=True)

def test_register(self):
"""Make sure registering works"""
rv = self.register('user1', 'default')
assert 'You were successfully registered and can login now' in rv.data
rv = self.register('user1', 'default')
assert 'The username is already taken' in rv.data
rv = self.register('', 'default')
assert 'You have to enter a username' in rv.data
rv = self.register('meh', '')
assert 'You have to enter a password' in rv.data
rv = self.register('meh', 'x', 'y')
assert 'The two password do not match' in rv.data
rv = self.register('meh', 'foo', email='broken')
assert 'You have to enter a valid email address' in rv.data

接下来测试登录、登出功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def login(self, username, password):
"""Helper function to sign in"""
return self.app.post('/login', data={
'username': username,
'password': password,
}, follow_redirects=True)

def register_and_login(self, username, password):
"""Register an account then login"""
self.register(username, password)
return self.ogin(username, password)

def logout(self):
"""Helper function to logout"""
return self.app.get('/logout', follow_redirects=True)

def test_login_logout(self):
"""Make sure logging in and logging out works"""
rv = self.register_and_login('user1', 'default')
assert 'You were logged in' in rv.data
rv = self.logout()
assert 'You were logged out' in rv.data
rv = self.login('user1', 'wrongpassword')
assert 'Invalid password' in rv.data
rv = self.login('user2', 'default')
assert 'Invalid username' in rv.data

测试发表微博功能:

1
2
3
4
5
6
7
8
9
10
11
def add_message(self, text):
"""Helper function to add a message"""
rv = self.app.post('/add_message', data={'text': text},
follow_redirects=True)
return rv

def test_add_message(self):
"""Check if adding messages works"""
self.register_and_login('foo', 'default')
rv = self.add_message('test message 1')
assert 'test message 1' in rv.data

测试时间线和关注功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def test_timelines(self):
"""Make sure that timelines work"""
self.register_and_login('foo', 'default')
self.add_message('the message by foo')
self.logout()
self.register_and_login('bar', 'default')
self.add_message('the message by bar')
rv = self.app.get('/public')
assert 'the message by foo' in rv.data
assert 'the message by bar' in rv.data

# test bar's timeline
rv = self.app.get('/')
assert 'the message by foo' not in rv.data
assert 'the message by bar' in rv.data

# test follow function
rv = self.app.get('/foo/follow', follow_redirects=True)
assert 'You are now following "foo"' in rv.data

rv = self.app.get('/')
assert 'the message by foo' in rv.data
assert 'the message by bar' in rv.data

# test the user timeline
rv = self.app.get('/bar')
assert 'the message by foo' not in rv.data
assert 'the message by bar' in rv.data
rv = self.app.get('/foo')
assert 'the message by foo' in rv.data
assert 'the message by bar' not in rv.data

# test unfollow function
rv = self.app.get('/foo/unfollow', follow_redirects=True)
assert 'You are no longer following "foo"' in rv.data
rv = self.app.get('/')
assert 'the message by foo' not in rv.data
assert 'the message by bar' in rv.data

Step 6: 部署程序

Web app在经过测试后,就可以部署上线了,以便用户通过互联网进行访问。下面我们将使用Heroku来部署我们的MiniWeibo。

  1. 安装、注册Heroku

首先下载安装Heroku Toolbelt。然后注册一个免费的账号。在终端中登录:

1
$ heroku login

  1. 配置部署文件

尽管Flask为我们提供了内置的wsgi服务器,但是在实际生产环境中,我们需要更换一个生产环境可用的服务器。我们选择gunicorn。

1
$ pip install gunicorn

接下来我们设置部署需要的Procfile文件

1
web gunicorn miniweibo:app

除了Procfile文件,还要自动创建一个Python依赖包文件。

1
pip freeze > requirements.text

  1. 利用git提交所有修改
1
2
$ git add -A
$ git commit -m "Add files for deploying at Heroku"
  1. 创建Heroku App并提交部署
1
2
$ heroku create miniweibo
$ git push heroku master

在完成部署后,Heroku会给出已部署的web应用的地址。
接下来我们在浏览器中输入mini-weibo.herokuapp.com就可以访问我们的MiniWeibo啦!