Odoo入门(八)—— 测试类与代码调试

编写测试类,Debug代码。

  • 开发中测试与debug代码是十分重要的一个环节。本章中我们将要使用自动测试来健全我们前几章创建的Todo应用。

单元测试。

  • 像python这样没有编译过程的动态语言,类型错误是非常常见的,所以使用单元测试来健壮我们的代码是非常重要的。
  • 编写测试类时,我们需要有2个目标 。
    1. 需要扩大测试范围。尽量包含所有代码。设计能够涵盖所有代码的测试实例
    2. 判断代码是否正确。 针对第二个目标,我们的测试类代码实例编写就会变得比较容易。

添加单元测试

  • Odoo中,添加测试类python代码我们一般会新建一个名为tests的子文件夹。
  • 创建 tests/test_wizard.py 文件,添加代码
from odoo.tests.common import TransactionCase
class TestWizard(TransactionCase):
    def setUp(self, *args, **kwargs):
        super(TestWizard,self).setUp(*args, **kwargs)
        # Add test setup code here...

    def test_populate_tasks(self):
        "Populate tasks buttons should add 5 tasks"
        # Add test code
  • Odoo 提供了一些用于测试的python类,TransactionCase 为每个测试提供了一个单独的事务处理,会在运行完一个测试实例后回到测试之前的状态。我们也可以使用 SingleTransactionCase 这个基类,它是把所有的测试都一起执行,直到最后一个测试完成才回到初始状态。这在需要保持测试之后的状态十分有用。
  • setUp() 方法: 这个方法是用来为测试实例准备基础数据跟存放变量的。我们通常在其中把基础数据作为类变量存储以方便每个测试实例使用。
  • 测试方法以 test_ 为前缀。类似test_populate_tasks().这样就能被自动化测试单元自动获取到。
  • 自动测试过程中,test_前缀测试方法会按序被调用。因为我们是基于TransactionCase这个基类的.所以在每个测试方法完成后都会回滚到测试开始的状态.在测试过程中,每个测试方法被执行时,Odoo的控制台会打印出相应的信息.
  • Odoo的测试类是基于python的unittest原生库的,更多的操作可以直接参考unittest库.

编写一个测试实例

  • 我们来扩展我的 test_populate_task() 方法。这个测试方法是用来测试do_populate_tasks()这个方法的.首先我们要在测试基础数据中定义2条Todos应用任务记录.所以测试代码如下:

    def test_populate_tasks(self):
        "Populate tasks buttons should add 5 tasks"
        self.wizard.do_populate_tasks()
        count = len(self.wizard.task_ids)
        self.assertEqual(count, 2, 'number wrong')
    
  • 第一行的docstring 是用来描述我们的测试实例的。它会在测试实例被执行时输出在控制台上。

  • 判断测试成功与否的关键在于断言方法。这里的assertEqual()方法表示对count的值与2进行判断。具体用法就是基于python的unittest库的。里面所有的断言方法可以去查看python api。

  • 添加一个新的测试实例,测试 do_mass_update() 方法。

    def test_mass_change(self):
        'Mass change deadline'
        self.wizard.do_populate_tasks()
        self.wizard.new_deadline = self.todo1.date_deadline
        self.wizard.do_mass_update()
        self.assertEqual(
        self.todo1.date_deadline,
        self.todo2.date_deadline,
    	)
    

这个测试实例很好理解,首先调用了do_populate_tasks()方法,把我们的两个task记录获取到向导表单中,然后把第一个任务的截止日期作为新的截止日期,执行do_mass_update()方法.最后进行断言任务1跟任务2的截止日期肯定是相同的.

设置测试类基础数据

  • 在使用测试前,应该先把基础数据准备好,如添加2条用以进行测试的任务记录. 我们在使用测试类实例时,为了能测试不同的用户权限,可以使用sudo()这个方法来改变当前的用户环境.

    class TestWizard(TransactionCase):
        def setUp(self, *args, **kwargs):
    	super(TestWizard,self).setUp(*args, **kwargs)
    	self.env['todo.task'].search([('is_done','=',False)]).write({'is_done':True})
    	# Demo user will be used to run tests
    	demo_user = self.env.ref('base.user_demo')
    	# Create two Todo tasks to use in tests
    	t0 = date.today()
    	Todo = self.env['todo.task'].sudo(demo_user)
    	self.todo1 = Todo.create({
    	    'name': 'Todo1',
    	    'date_deadline': fields.Date.to_string(t0),
    				 	})
    	self.todo2 = Todo.create({
    	    'name': 'Todo2',
    					})
    	# Create Wizard instance to use in tests
    	Wizard = self.env['todo.wizard'].sudo(demo_user)
    	self.wizard = Wizard.create({})
    

为了测试我们的向导逻辑,我们首先定义了2条新的Todo tasks 记录, 然后指定测试用户为Demo用户. 最后创建向导实例来作为测试的基础数据.

测试异常

  • 很多时候我们需要测试代码中是否会有异常抛出. 在下面的例子中,测试方法test_count()使用了with self.assertRaises()来测试向导表单中的方法do_count_tasks()是否能抛出Warning异常.

    from odoo.exceptions import Warning
        def test_count(self):
    	"Test count button"
    	with self.assertRaises(Warning) as e:
    	    self.wizard.do_count_tasks()
    	self.assertIn(' 2 ', str(e.exception))
    

上面的代码中,我们还对抛出异常后的异常信息进行断言,判断2是否包含在异常信息中。因为根据do_count_tasks()这个方法,会把任务的总数作为警告信息抛出。我们正好可以使用2来判断是否把总的任务个数传递出来。

运行测试类

  • 使用命令

    ./odoo-bin -d todo --test-enable -i todo_wizard --stop-after-init --addons-path="..."
    

来执行我们已经编写好的测试实例.

关于YAML 测试

  • 这种测试方式在Odoo10中已经基本不使用了 .

开发工具

  • 一些常用的服务端开发选项. 我们在第一章的时候就介绍过,在开启Odoo服务时,添加选项--dev 能够开启Odoo自带的开发者模式,其中有一些功能对开发来说是十分有效率的.
    • 当应用模块中发现异常时,会进入到debug模式
    • 自动重载Python代码.省去手动重启.
    • 能够直接读取xml文件的变动来改变视图.不用自己去手动更新. 默认情况下, 在dev开启后会使用python自带的调试器 pdb. Odoo同时支持 ipdbpudb.

调试代码

  • 调试代码在开发过程中十分重要.我们经常会采用打断点来运行我们的程序,在一步步的程序运行过程中分析各个变量,找到我们代码bug所在.

Python 调试器

  • 在Odoo中我们使用 pdb 作为调试器来对代码进行调试。
  • 调试代码过程关键在于插入断点,在认为可能出现bug的代码前插入断点。
import pdb; pdb.set_trace()

然后重启服务,Odoo程序就会在断点的地方停止,出现pdb 交互界面,等待你的进一步输入。注意,手动插入pdb断点,不需要设置--dev选项也可以进行。如果设置了--dev选项,会在应用报错后自动进入pdb交互模式。 pdb调试常用指令:

  • h(help) :显示pdb命令帮助
  • p(print) :输出
  • pp(pretty print) : 更加美观的输出,一般在打印字典或者列表结构时使用.
  • l(list) : 展示当前行代码段
  • n(next) : 执行下一行
  • s(step) : 进入当前指令,通常是进入方法体内
  • c(continue) : 继续执行,直到下一个断点
  • u(up) : 在call stack中上移一层
  • d(down) : 在call stack 中下移一层

一个简单的调试会话事例

  • 我们首先在do_populate_tasks这个向导方法中插入断点。
    def do_populate_tasks(self):
        self.ensure_one()
        import pdb; pdb.set_trace()
        Task = self.env['todo.task']
        ....

重启服务端,打开To-do Tasks Wizard 表单,点击Get All 按钮,然后返回命令行窗口,会看到服务端进入了pdb调试交互界面。

![命令行pdb界面](http://upload-images.jianshu.io/upload_images/6865906-826d430834c00d14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

开头的第一行表明了目前处于Python文件中的路径跟目前执行的代码的行号。第二行展示了Odoo将要执行的命令。

  • 在调试会话中,服务日志记录会被不停的打印,虽然不会妨碍调试,但是大段的日志记录打印出来也很烦,我们可以在启动服务时添加参数--log-handler=werkzeug: CRITICAL 来避免日志的打印。或者还可以直接把日志等级调低使用--log-level=warn选项

可选的python调试工具

  • 除了Odoo默认提供的pdb调试工具,还有像ipdb,pudb这样的更加先进的调试工具

  • ipdb: 添加了语法高亮

    • 安装: sudo pip install ipdb
    • 使用: `import ipdb; ipdb.set_trace()
  • pudb: 使用了图形化界面让变量值,当前栈信息更好的显示出来。

    pudb调试器

    • 安装: sudo apt-get install python-pudb或者 sudo pip install pubd 使用方法也类似:

      import pudb; pudb.set_trace()