在Django下测试与调试REST API的方法详解
对于大多数研发人员来说,都期望能找到一个良好的测试/调试方法,来提高工作效率和快速解决问题。所谓调试,偏重于对某个bug的查找、定位、修复;所谓测试,是检验某个功能是否达到预期效果。测试发现问题后进行调试,从而解决问题。
对于后台研发来说,往往没有客户端研发(Windows/Android等等)那样简单有效的DEBUG方法,比如Step by Step。虽然目前有很多IDE可以实现本地调试,但是因为后台研发的环境复杂,你很难在一台机器上模拟所有的环境,比如线上的数据库只能在内网访问等等,所以很多时候需要在服务器(DEV/STG/PROD)上进行调试和测试工作。本文尝试介绍一些Django Web服务下调试/测试REST API的方法,因为是方法,那自然仁者见仁智者见智,所以也许你觉得会有用,也许会觉得毫无道理。
首先要说的是,在服务器环境,针对Django Web服务,print log是最简单有效的方法。python的语言特性决定了你可以随时添加log,重新run一下就可以看到结果。本文接下来要说的,并不是什么超脱于pring log的超级技巧,而是如何编写代码、利用工具进行有效的调试/测试工作,并尽量做到减少重复性工作。
对于任何一个REST API来说,需要进行测试的内容包括两部分:请求中的各个功能模块是否正常、整个请求是否正常。对于研发人员来说,写完代码后当然要进行一番测试来确认代码是否正常工作、是否符合需求。这时,有人选择先测试一下整个请求是否正常,如果正常就万事大吉,交付给测试同事测试了,如果不正常则进一步查看是哪个功能模块出现问题;有人选择先逐一测试各个功能模块是否正常,在确保各功能模块正常后,再进一步测试整个请求是否正常,然后交付测试。这两种行事风格并没有特别的利弊之说,最好是在不同的情形下选择使用,比如对于简单的功能API,可以使用第一种,而对于复杂的功能API,选择第二种。不管行事风格如何,都会涉及都前面所述的两部分测试内容,即分与合的两部分测试。Django下,常见的有这么两种方法,或者说目前在我们项目组使用最广泛的两种方法:
1. 对于功能模块,使用python manage.py shell来进行调试与测试。该命令帮助我们启动一个python 的shell环境,并自动加载了Django的上下文信息,我们可以在里面import任何函数和类,构建参数,调试/测试其相关的功能。如果需要定位问题,一种方法是在源文件中加入更多的log,一种方法是将该功能模块逐行敲一遍,看看在哪边出现了问题。
2. 对于整个请求,使用工具curl(linux下)或者Advanced REST Client(Windows下,Chome的一个插件)来构建一个Request,发送给后台服务。笔者使用更多的是第二个,其可以构建各个Method的Request,并且可以保存相应的请求,还可以分享你保存的列表给别人使用。发送请求后,通过后台log来查看请求是否正常及相关的异常错误问题,你可以使用tail -f /var/log/test.log来查看动态log信息。
在合适的位置增加规范化的log对于后台服务来说,是一件非常重要的事情,能帮助我们在线上环境及时发现问题,log的书写应该包括相应的Tag,ErrorCode等等信息,方便查找。
上述两个方法,可以很好的对Django下的Web服务进行调试/测试,但是却有着一些缺憾。对应第一种方法,通过python manage.py shell进行功能测试,意味着每次都需要启动一个shell,每次都需要将相应的代码敲一遍,存在着重复性的工作,其实这种方法更适用于应对特殊的CASE,而不是针对Common的功能进行调试/测试。对于第二种方法,首先,当你想增加一些log时,你需要重启一下服务才能生效,而这种情况在生产环境就比较棘手;其次,通过这种方法进行测试,你没有办法在程序中处理一些Response的结果。
Python和Django自带了一些测试框架和工具,而充分利用这些,正好可以解决上述两种方法的缺憾。常用的一些测试框架和工具有:
1)单元测试框架TestCase;
2)Django的Test Client,类似curl的工具,是一个类似内置浏览器的工具,可以发起一个请求,并拿到对应的response内容;
3)RequestFactory,允许构建一个request参数,实现象调用函数一样调用一个view的请求函数,可以绕过middleware。
结合前面两种方法的缺憾和上述的测试框架/工具,下面介绍两种笔者常用的两种调试/测试方法:
1. 对于功能模块,采用单元测试的方法。单元测试常见于TDD的研发流程中,一方面可以验证所写的程序模块运行后的行为是否符合设计的测试用例,另一方面可以在修改代码后,快速验证是否改出了问题,是否与原有行为保持一致。采用单元测试的方法,既可以实现功能模块的调试/测试功能,也能有效的避免重复性工作,有效的弥补了前面所述的第一种方法的缺点。
python本身自带了一套unittest框架,而diango对于它又做了一层封装,可以根据喜好选择使用,笔者通常还是习惯使用python自带的单元测试框架。一个简单的单元测试代码如下:
import unittest class TestStringMethods(unittest.TestCase): def test_upper(self): self.assertEqual('foo'.upper(), 'FOO') def test_isupper(self): self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) def test_split(self): s = 'hello world' self.assertEqual(s.split(), ['hello', 'world']) # check that s.split fails when the separator is not a string with self.assertRaises(TypeError): s.split(2) if __name__ == '__main__': unittest.main()
2. 对于整个请求,采用RequestFactory来实现类函数式调用的方法,从而可以实现调用view的请求函数。采用这个方法,可以有效避免前面第二种方法中需要重启服务的缺陷,当你修改了代码后,只要重新reload一下模块,即可实现新的调用;同时,你也可以在代码中拿到对应的response内容,做必要的校验处理。
然而,对于我们使用rest framework 框架来做REST API开发来说,Django原生的RequestFactory还是有一点缺陷,就是不能做authenticate。因为rest framework中的authenticate是做在request前面的,而不是在middleware中,所以采用原生的RequestFactory无法绕过authenticate。不过,rest framework又重新封装了一个APIRequestFactory类,提供了相关的模拟鉴权功能,常见的用法如下:
from rest_framework.test import APIRequestFactory from rest_framework.test import force_authenticate # Using the standard RequestFactory API to create a form POST request factory = APIRequestFactory() request = factory.post('/notes/', {'title': 'new idea'}, format='json') # force authenticate user = User.objects.get(username='olivia') force_authenticate(request, user=user) # call the view request view = AccountDetail.as_view() response = view(request)
上面所述的方法,只是笔者目前常用的四种方法,在实际工作中根据需要进行选择使用。当然,我相信还有更多更好的其他方法。
这篇在Django下测试与调试REST API的方法详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持【听图阁-专注于Python设计】。