在之前的文章中介绍过在flask中进行单元测试的方法,目前我们的代码中存在下面的单元测试:
classBasicTestCase(unittest.TestCase):
def Setup(self):
self.app=create_app('testing')
self.app_context=self.app.app_context()
self.app_context.push()
db.create_all()
def teardown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_app_exits(self):
self.assertFalse(current_app is None)
def test_app_is_testing(self):
self.assertFalse(current_app.config['TESTING'])
并且,我们在manager的命令行中增加了运行单元测试的命令:
@manager.command
def test():
"""Run the unittests"""
import unittest
tests=unittest.TestLoader().discover('test')
unittest.TextTestRunner(verbosity=2).run(tests)
可以通过命令行,按照下面的方式进行单元测试:
(flasky)hyman@hyman-VirtualBox:~/Github/flaskTs$ python manager.py test
test_app_exits(test_basic.BasicTestCase) ... ok
test_app_is_testing(test_basic.BasicTestCase) ... ok
----------------------------------------------------------------------
Ran 2 tests in0.005s
OK
Flask中可以使用coverage模块来统计进行单元测试的代码覆盖率,其安装方式如下:
pip installcoverage
安装完成后,我们将coverage作为一个自定义命令的选项附加到test命令中:
COV=None
ifos.environ.get('COVERAGE'):
import coverage
COV=coverage.coverage(branch=True,include='*')
COV.start()
@manager.command
deftest(coverage=False):
"""Run the unittests"""
if coverage and notos.environ.get('COVERAGE'):
import sys
os.environ['COVERAGE']='1'
os.execvp(sys.executable,[sys.executable]+sys.argv)
import unittest
tests=unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
if COV:
COV.stop()
COV.save()
print('Coverage:')
COV.report()
COV.erase()
首先在全局的范围内,我们先检查是否具有'COVERAGE'环境变量,如果定义了该变量,就利用coverage的coverage函数构造一个coverage对象COV。coverage函数有两个参数,第一个参数branch设置为True表示检查的模块包含指定目录下的子目录,而第二个参数include是在指定目录,设置成’*’的意思就是设置当前目录和当前目录下的模块位检查模块。
而在test()函数中,附加的命令行参数coverage会作为test()函数的一个bool类型的参数,如果命令行附加了该参数,coverage将会等于True。在函数内部,先判断是否coverage已经是否定义了'COVERAGE'环境变量。因为我们定义以及执行COV的代码为全局代码,在执行test命令行之前已经执行过,所以在判断完之后,调用了os.execvp重新执行了程序。接下来COV变量不再是None,就可进行if COV后面的操作。
执行的程序结果如下,由于将当前目录下以及子目录下所有的模块都作为了统计对象,所以结果中包含了flask本身自带的一些模块。
Flask中的成员函数test_client()可以返回一个客户端对象,可以模拟Web客户端,用来同Web服务端进行交互。它可以测试Web程序,也可以用来测试Rest API,下面就该客户端的使用进行讨论。
新建一个FlaskClientTest单元测试类如下:
classFlaskClientTest(unittest.TestCase):
def setUp(self):
self.app=create_app('testing')
self.app_context=self.app.app_context()
self.app_context.push()
db.create_all()
self.client=self.app.test_client()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_home_page(self):
response=self.client.get(url_for('main.index'))
self.assertTrue('Home' inresponse.get_data(as_text=True))
def test_register(self):
response=self.client.post(url_for('main.register'),data={
'email':'[email protected]',
'name':'Hyman',
'password1':'123',
'password2':'123'})
self.assertTrue(response.status_code==302)
test_home_page是用来进行测试主页的测试用例,这里使用self.client.get(url_for('main.index'))的方法,使用GET方法访问相应的url,并获取响应结果。然后调用response的get_data()获取响应结果,这里面设置as_text=True,会把结果格式化成一个字符串,进而判断‘Home’是否在这个字符串中。
test_register用来模拟注册用户过程,对注册的路由进行测试。这里采用了POST的方法发送了一个表单,而data中是对表单各个字段的赋值,这里面的key值一定要和我们定义的注册表单的字段名称一致。由于注册路由最终向主页进行了重定向,届时服务端回返回给浏览器302的状态码,我们通过判断响应的status_code是否是302而判断测试是否通过。
接下来,测试下上篇文章编写的一个用来增加新博客的Rest API:
deftest_posts(self):
response=self.client.post(
url_for('main.new_post'),
data=json.dumps({'body':'I am a newpost'}),
content_type='application/json')
self.assertTrue(response.status_code==200)
这里面需要注意的就是,测试Rest API时需要在client的post方法中显示的指定传送的数据类型content_type为'application/json',而且传送的data需要用json.dumps()方法将数据格式化成json的格式。assertTrue中还是检验响应的status_code,如果文章创建成功,服务端会返回200的状态码,所以在这里同200进行了比较。
Selenium可以自动启动浏览器,模拟用户点击浏览器的连接,selenium的安装方法如下:
pip install selenium
我们进行测试的思路时,利用后台线程启动服务端,然后在主线程中利用selenium自动操作firefox进行自动化的测试。在使用selenium之前,我们先要定义一个路由可以关闭服务端的运行,以使在测试结束后服务端自动结束运行。
@main.route('/shutdown')
defserv_shutdown():
shutdown=request.environ.get('werkzeug.server.shutdown')
shutdown()
return 'shuting down'
werkzeug服务器提供了一个shutdown函数供外部使用,在上述的视图函数中,我们获取到了这个函数,并进行调用,这样通过访问http://localhost:5000/shutdown这个url我们就可以实现关闭服务端。
接下来是使用selenium进行自动化测试的代码:
#coding:utf-8
import unittest
from flaskimport current_app,url_for,json
from app importcreate_app,db
from seleniumimport webdriver
classSeleniumTestCase(unittest.TestCase):
client=None
@classmethod
def setUpClass(cls):
#启动firefox
try:
cls.client=webdriver.Firefox()
except:
pass
if cls.client:
cls.app=create_app('testing')
cls.app_context=cls.app.app_context()
cls.app_context.push()
db.create_all()
threading.Thread(target=cls.app.run).start()
@classmethod
def tearDownClass(cls):
if cls.client:
cls.client.get('http://localhost:5000/shutdown')
cls.client.close()
db.drop_all()
db.session.remove()
cls.app_context.pop()
def setUp(self):
if not self.client:
self.skipTest('Firefox isinvailable')
def tearDown(self):
pass
def test_home_page(self):
self.client.get('http://localhost:5000/')
self.assertTrue(re.search('Home',self.client.page_source))
通过修饰器classmethod定义了setUpClass()和tearDownClass()两个成员函数,分别在该类中的所有的测试用例执行之前和之后运行,而setUp()和tearDown()是在每个测试用例执行前后都运行。在setUpClass()中,通过webdriver.Firefox()获取到了Firefox()浏览器对象,并在threading.Thread(target=cls.app.run).start()中在后台线程运行服务端,然后在测试用例test_home_page中我们通过这个浏览器对象访问'http://localhost:5000/',并利用正则表达式判断返回的结果中是否包含'Home'字符串。在所有的测试完成之后,在tearDownClass()中访问'http://localhost:5000/shutdown'从而完成了服务端的关闭。至此,一个完整的selenium测试流程完成。
Selenium的强大在于,它可以操作浏览器实现自动化的测试,仿佛就像有人在浏览器上点击一样,它创建的Firefox客户端对象提供了很多方法可以在Web界面找到相关的元素并进行后续的操作。比如find_element_by_link_text()方法,可以根据超链接的名称找到该链接,并在调用其Click()方法模拟点击超链接;find_element_by_name()方法根据根据名字找到对应的字段,然后可以使用期send_keys()的方法在各个字段中填入值。
Github位置:
https://github.com/HymanLiuTS/flaskTs
克隆本项目:
Git clone Git@github.com:HymanLiuTS/flaskTs.Git
获取本文源代码:
Git checkout FL40