GVKun编程网logo

断言在Python单元测试中已调用方法(python unittest 断言)

8

如果您对断言在Python单元测试中已调用方法和pythonunittest断言感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解断言在Python单元测试中已调用方法的各种细节,并对python

如果您对断言在Python单元测试中已调用方法python unittest 断言感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解断言在Python单元测试中已调用方法的各种细节,并对python unittest 断言进行深入的分析,此外还有关于Django中的单元测试以及Python单元测试、Mock在Python单元测试中的使用、Python单元测试、python单元测试---unittest的实用技巧。

本文目录一览:

断言在Python单元测试中已调用方法(python unittest 断言)

断言在Python单元测试中已调用方法(python unittest 断言)

假设我在Python单元测试中具有以下代码:

aw = aps.Request("nv1")aw2 = aps.Request("nv2", aw)

有没有一种简单的方法可以断言aw.Clear()在测试的第二行期间调用了特定方法(在我的情况下)?例如是否有这样的事情:

#pseudocode:assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw))

答案1

小编典典

我为此使用Mock(在py3.3
+上现在是unittest.mock):

from mock import patchfrom PyQt4 import Qt@patch.object(Qt.QMessageBox, ''aboutQt'')def testShowAboutQt(self, mock):    self.win.actionAboutQt.trigger()    self.assertTrue(mock.called)

对于您的情况,它可能看起来像这样:

import mockfrom mock import patchdef testClearWasCalled(self):   aw = aps.Request("nv1")   with patch.object(aw, ''Clear'') as mock:       aw2 = aps.Request("nv2", aw)   mock.assert_called_with(42) # or mock.assert_called_once_with(42)

Mock支持许多有用的功能,包括修补对象或模块的方式以及检查是否调用了正确的东西等。

买者自负! (请当心!)

如果您输入错误的assert_called_with(到assert_called_onceassert_called_wiht)您的测试可能仍在运行,因为Mock会认为这是一个模拟的函数并且很乐意进行,除非您使用autospec=true。有关更多信息,请阅读assert_call_once:Threat或Menace。

Django中的单元测试以及Python单元测试

Django中的单元测试以及Python单元测试

Python单元测试

  是用来对一个模块、一个函数或者一个类进行正确性检验的测试工作。

  在Python中unittest是它内置的单元测试框架,单元测试与功能测试都是日常开发中必不可少的部分。

  比如对函数abs(),我们可以编写出一下几个测试用例:

  •  输入正数,比如1,1.2,0.99,我们期待返回值与输入相同
  •  输入负数,比如-1,-1.2,-0.99,我们期待返回值与输入值相反
  •  输入0,我们期待返回0
  •  输入非数值类型,比如None,[],{},我们期待抛出TypeError

    把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。

一个简单的测试用例

  定义一个类,简单的实现add、sub两方法,并对其进行单元测试

  待测试文件m1.py

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 
 5 class MyClass(object):
 6     def __init__(self, x, y):
 7         self.x = int(x)
 8         self.y = int(y)
 9 
10     def add(self):
11         return self.x + self.y
12 
13     def sub(self):
14         return self.x - self.y

  在m1.py同级目录下创建test.py测试文件,使用unittest单元测试框架对MyClass的方法进行测试

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import unittest
 5 from m1 import MyClass
 6 
 7 
 8 class MyclassTest(unittest.TestCase):
 9     def setUp(self):
10         self.calc = MyClass(7, 5)
11 
12     def tearDown(self):
13         pass
14 
15     def test_add(self):
16         ret = self.calc.add()
17         self.assertEqual(ret, 12)
18 
19     def test_sub(self):
20         ret = self.calc.sub()
21         self.assertEqual(ret, 2)
22 
23 
24 # if __name__ == ''__main__'':
25 #     suite = unittest.TestSuite()
26 #     suite.addTest(MyclassTest(''test_add''))
27 #     suite.addTest(MyclassTest(''test_sub''))
28 #
29 #     runner = unittest.TextTestRunner()
30 #     runner.run(suite)
31 
32 if __name__ == ''__main__'':
33     unittest.main()

 运行测试

1 python -m unittest test
2 ..
3 ----------------------------------------------------------------------
4 Ran 2 tests in 0.000s
5 
6 OK

总结:

  编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承,unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEqual()方法

运行单元测试: 

1 一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在test.py的最后加上两行代码:
2 if __name__ == ''__main__'':
3     unittest.main()
4 这样就可以把test.py当做正常的python脚本运行:
5 $ python test.py
6 另一种方法是在命令行通过参数-m unittest直接运行单元测试:
7 $ python -m unittest test

 unittest框架小知识点梳理

  1. test fixture:是初始化和清理测试数据及环境,通过重写TestCase的setUp()和tearDown()方法实现

    两方法使用:设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码:    

1 class MyclassTest(unittest.TestCase):
2 
3     def setUp(self):
4         print(''setUp...'')
5 
6     def tearDown(self):
7         print(''tearDown...'')

  2.test case:是测试用例

  3.test suite:是测试用例的集合,通过addTest加载TestCase到TestSuite中,返回一个TestSuite实例

  4.test runner:是运行测试用例并返回结果,通过TextTestRunner类提供的run()方法来执行test suite或test case  

 注意:

  单元测试通过了并不意味着程序就没有bug了,但是不通过程序肯定有bug

Django中的单元测试

  在创建app的时候,在app目录下自动生成tests.py文件

  Model部分单元测试用例  

1 from django.db import models
2 
3 # Create your models here.
4 
5 
6 class Book(models.Model):
7     title = models.CharField(max_length=32)
8     price = models.DecimalField(max_digits=10, decimal_places=2)

  测试用例代码  

 1 from django.test import TestCase
 2 from app01.models import Book
 3 # Create your tests here.
 4 
 5 
 6 class BookModelTest(TestCase):
 7     def setUp(self):
 8         Book.objects.create(title=''斗破苍穹'', price=10.99)
 9 
10     def test_book_model(self):
11         from decimal import Decimal
12         result = Book.objects.get(title=''斗破苍穹'')
13         self.assertEqual(result.price, Decimal(''10.99''))

运行测试在项目目录下运行:

1 python manage.py test
2 Creating test database for alias ''default''...
3 System check identified no issues (0 silenced).
4 .
5 ----------------------------------------------------------------------
6 Ran 1 test in 0.002s
7 
8 OK
9 Destroying test database for alias ''default''...

视图部分单元测试用例  

1 from django.shortcuts import render
2 
3 # Create your views here.
4 
5 
6 def index(request):
7     return render(request, ''index.html''

 在app01/tests.py文件中添加测试用例代码:

1 class IndexPageTest(TestCase):
2     """测试index页面"""
3     def test_index_page_renders_index_template(self):
4         """测试index视图"""
5         response = self.client.get(''/index/'')
6         self.assertEqual(response.status_code, 200)  # 判断状态码
7         self.assertTemplateUsed(response, ''index.html'')  # 判断渲染的模板是否正确

运行单元测试:

1 python manage.py test
2 Creating test database for alias ''default''...
3 System check identified no issues (0 silenced).
4 ..
5 ----------------------------------------------------------------------
6 Ran 2 tests in 0.044s
7 
8 OK
9 Destroying test database for alias ''default''...

 

Mock在Python单元测试中的使用

Mock在Python单元测试中的使用

《Mock在Python单元测试中的使用》要点:
本文介绍了Mock在Python单元测试中的使用,希望对您有用。如果有疑问,可以联系我们。

本文讲述的是 Python 中 Mock 的使用.

如何执行单元测试而不用考验你的耐心

很多时候,我们编写的软件会直接与那些被标记为“垃圾”的服务交互.用外行人的话说:服务对我们的应用程序很重要,但是我们想要的是交互,而不是那些不想要的副作用,这里的“不想要”是在自动化测试运行的语境中说的.例如:我们正在写一个社交 app,并且想要测试一下 “发布到 Facebook” 的新功能,但是不想每次运行测试集的时候真的发布到 Facebook.

Python 的 unittest 库包含了一个名为 unittest.mock 或者可以称之为依赖的子包,简称为 mock —— 其提供了极其强大和有用的方法,通过它们可以模拟mock并去除那些我们不希望的副作用.

Python

注意:mock最近被收录[1]到了python3.3的标准库中;先前发布的版本必须通过PyPI[2]下载Mock库.

恐惧系统调用

再举另一个例子,我们在接下来的部分都会用到它,这是就是系统调用.不难发现,这些系统调用都是主要的模拟对象:无论你是正在写一个可以弹出 CD 驱动器的脚本,还是一个用来删除 /tmp 下过期的缓存文件的 Web 服务,或者一个绑定到 TCP 端口的 socket 服务器,这些调用都是在你的单元测试上下文中不希望产生的副作用.

作为一个开发者,你需要更关心你的库是否成功地调用了一个可以弹出 CD 的系统函数(使用了正确的参数等等),而不是切身经历 CD 托盘每次在测试执行的时候都打开了.(或者更糟糕的是,弹出了很多次,在一个单元测试运行期间多个测试都引用了弹出代码!)

同样,保持单元测试的效率和性能意味着需要让如此多的“缓慢代码”远离自动测试,比如文件系统和网络访问.

对于第一个例子来说,我们要从原始形式换成使用 mock 重构一个标准 Python 测试用例.我们会演示如何使用 mock 写一个测试用例,使我们的测试更加智能、快速,并展示更多关于我们软件的工作原理.

一个简单的删除函数

我们都有过需要从文件系统中一遍又一遍的删除文件的时候,因此,让我们在 Python 中写一个可以使我们的脚本更加轻易完成此功能的函数.

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import os
  4. def rm(filename):
  5.    os.remove(filename)

很明显,我们的 rm 方法此时无法提供比 os.remove 方法更多的相关功能,但我们可以在这里添加更多的功能,使我们的基础代码逐步改善.

让我们写一个传统的测试用例,即,没有使用 mock

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from mymodule import rm
  4. import os.path
  5. import tempfile
  6. import unittest
  7. class RmTestCase(unittest.TestCase):
  8.    tmpfilepath = os.path.join(tempfile.gettempdir(), "tmp-testfile")
  9.    def setUp(self):
  10.        with open(self.tmpfilepath, "wb") as f:
  11.            f.write("Delete me!")
  12.    def test_rm(self):
  13.        # remove the file
  14.        rm(self.tmpfilepath)
  15.        # test that it was actually removed
  16.        self.assertFalse(os.path.isfile(self.tmpfilepath), "Failed to remove the file.")

我们的测试用例相当简单,但是在它每次运行的时候,它都会创建一个临时文件并且随后删除.此外,我们没有办法测试我们的 rm 方法是否正确地将我们的参数向下传递给 os.remove 调用.我们可以基于以上的测试认为它做到了,但还有很多需要改进的地方.

使用 Mock 重构

让我们使用 mock 重构我们的测试用例:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from mymodule import rm
  4. import mock
  5. import unittest
  6. class RmTestCase(unittest.TestCase):
  7.    @mock.patch('mymodule.os')
  8.    def test_rm(self, mock_os):
  9.        rm("any path")
  10.        # test that rm called os.remove with the right parameters
  11.        mock_os.remove.assert_called_with("any path")

使用这些重构,我们从根本上改变了测试用例的操作方式.现在,我们有一个可以用于验证其他功能的内部对象.

潜在陷阱

第一件需要注意的事情就是,我们使用了 mock.patch 方法装饰器,用于模拟位于 mymodule.os 的对象,并且将 mock 注入到我们的测试用例方法.那么只是模拟 os 本身,而不是 mymodule.os 下 os 的引用(LCTT 译注:注意 @mock.patch('mymodule.os') 便是模拟 mymodule.os 下的 os),会不会更有意义呢?

当然,当涉及到导入和管理模块,Python 的用法就像蛇一样灵活.在运行时,mymodule 模块有它自己的被导入到本模块局部作用域的 os.因此,如果我们模拟 os,我们是看不到 mock 在 mymodule 模块中的模仿作用的.

这句话需要深刻地记住:

模拟一个东西要看它用在何处,而不是来自哪里.

如果你需要为 myproject.app.MyElaborateClass 模拟 tempfile 模块,你可能需要将 mock 用于myproject.app.tempfile,而其他模块保持自己的导入.

先将那个陷阱放一边,让我们继续模拟.

向 ‘rm’ 中加入验证

之前定义的 rm 方法相当的简单.在盲目地删除之前,我们倾向于验证一个路径是否存在,并验证其是否是一个文件.让我们重构 rm 使其变得更加智能:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import os.path
  5. def rm(filename):
  6.    if os.path.isfile(filename):
  7.        os.remove(filename)

很好.现在,让我们调整测试用例来保持测试的覆盖率.

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from mymodule import rm
  4. import mock
  5. import unittest
  6. class RmTestCase(unittest.TestCase):
  7.    @mock.patch('mymodule.os.path')
  8.    @mock.patch('mymodule.os')
  9.    def test_rm(self, mock_os, mock_path):
  10.        # set up the mock
  11.        mock_path.isfile.return_value = False
  12.        rm("any path")
  13.        # test that the remove call was NOT called.
  14.        self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")
  15.        # make the file 'exist'
  16.        mock_path.isfile.return_value = True
  17.        rm("any path")
  18.        mock_os.remove.assert_called_with("any path")

我们的测试用例完全改变了.现在我们可以在没有任何副作用的情况下核实并验证方法的内部功能.

将文件删除作为服务

到目前为止,我们只是将 mock 应用在函数上,并没应用在需要传递参数的对象和实例的方法上.我们现在开始涵盖对象的方法.

首先,我们将 rm 方法重构成一个服务类.实际上将这样一个简单的函数转换成一个对象,在本质上这不是一个合理的需求,但它能够帮助我们了解 mock 的关键概念.让我们开始重构:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import os.path
  5. class RemovalService(object):
  6.    """A service for removing objects from the filesystem."""
  7.    def rm(filename):
  8.        if os.path.isfile(filename):
  9.            os.remove(filename)

你会注意到我们的测试用例没有太大变化:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from mymodule import RemovalService
  4. import mock
  5. import unittest
  6. class RemovalServiceTestCase(unittest.TestCase):
  7.    @mock.patch('mymodule.os.path')
  8.    @mock.patch('mymodule.os')
  9.    def test_rm(self, mock_path):
  10.        # instantiate our service
  11.        reference = RemovalService()
  12.        # set up the mock
  13.        mock_path.isfile.return_value = False
  14.        reference.rm("any path")
  15.        # test that the remove call was NOT called.
  16.        self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")
  17.        # make the file 'exist'
  18.        mock_path.isfile.return_value = True
  19.        reference.rm("any path")
  20.        mock_os.remove.assert_called_with("any path")

很好,我们知道 RemovalService 会如预期般的工作.接下来让我们创建另一个服务,将 RemovalService 声明为它的一个依赖:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import os.path
  5. class RemovalService(object):
  6.    """A service for removing objects from the filesystem."""
  7.    def rm(self, filename):
  8.        if os.path.isfile(filename):
  9.            os.remove(filename)
  10. class UploadService(object):
  11.    def __init__(self, removal_service):
  12.        self.removal_service = removal_service
  13.    def upload_complete(self, filename):
  14.        self.removal_service.rm(filename)

因为我们的测试覆盖了 RemovalService,因此我们不会对我们测试用例中 UploadService 的内部函数 rm 进行验证.相反,我们将调用 UploadService 的 RemovalService.rm 方法来进行简单测试(当然没有其他副作用),我们通过之前的测试用例便能知道它可以正确地工作.

这里有两种方法来实现测试:

  1. 模拟 RemovalService.rm 方法本身.
  2. 在 UploadService 的构造函数中提供一个模拟实例.

因为这两种方法都是单元测试中非常重要的方法,所以我们将同时对这两种方法进行回顾.

方法 1:模拟实例的方法

mock 库有一个特殊的方法装饰器,可以模拟对象实例的方法和属性,即 @mock.patch.object decorator 装饰器:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from mymodule import RemovalService, UploadService
  4. import mock
  5. import unittest
  6. class RemovalServiceTestCase(unittest.TestCase):
  7.    @mock.patch('mymodule.os.path')
  8.    @mock.patch('mymodule.os')
  9.    def test_rm(self, "Failed to not remove the file if not present.")
  10.        # make the file 'exist'
  11.        mock_path.isfile.return_value = True
  12.        reference.rm("any path")
  13.        mock_os.remove.assert_called_with("any path")
  14. class UploadServiceTestCase(unittest.TestCase):
  15.    @mock.patch.object(RemovalService, 'rm')
  16.    def test_upload_complete(self, mock_rm):
  17.        # build our dependencies
  18.        removal_service = RemovalService()
  19.        reference = UploadService(removal_service)
  20.        # call upload_complete, which should, in turn, call `rm`:
  21.        reference.upload_complete("my uploaded file")
  22.        # check that it called the rm method of any RemovalService
  23.        mock_rm.assert_called_with("my uploaded file")
  24.        # check that it called the rm method of _our_ removal_service
  25.        removal_service.rm.assert_called_with("my uploaded file")

非常棒!我们验证了 UploadService 成功调用了我们实例的 rm 方法.你是否注意到一些有趣的地方?这种修补机制(patching mechanism)实际上替换了我们测试用例中的所有 RemovalService 实例的 rm 方法.这意味着我们可以检查实例本身.如果你想要了解更多,可以试着在你模拟的代码下断点,以对这种修补机制的原理获得更好的认识.

陷阱:装饰顺序

当我们在测试方法中使用多个装饰器,其顺序是很重要的,并且很容易混乱.基本上,当装饰器被映射到方法参数时,装饰器的工作顺序是反向的[3].思考这个例子:

  1.    @mock.patch('mymodule.sys')
  2.    @mock.patch('mymodule.os')
  3.    @mock.patch('mymodule.os.path')
  4.    def test_something(self, mock_os_path, mock_sys):
  5.        pass

注意到我们的参数和装饰器的顺序是反向匹配了吗?这部分是由 Python 的工作方式[4]所导致的.这里是使用多个装饰器的情况下它们执行顺序的伪代码:

  1. patch_sys(patch_os(patch_os_path(test_something)))

因为 sys 补丁位于最外层,所以它最晚执行,使得它成为实际测试方法参数的最后一个参数.请特别注意这一点,并且在运行你的测试用例时,使用调试器来保证正确的参数以正确的顺序注入.

方法 2:创建 Mock 实例

我们可以使用构造函数为 UploadService 提供一个 Mock 实例,而不是模拟特定的实例方法.我更推荐方法 1,因为它更加精确,但在多数情况,方法 2 或许更加有效和必要.让我们再次重构测试用例:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from mymodule import RemovalService, "Failed to not remove the file if not present.")
  4.        # make the file 'exist'
  5.        mock_path.isfile.return_value = True
  6.        reference.rm("any path")
  7.        mock_os.remove.assert_called_with("any path")
  8. class UploadServiceTestCase(unittest.TestCase):
  9.    def test_upload_complete(self, mock_rm):
  10.        # build our dependencies
  11.        mock_removal_service = mock.create_autospec(RemovalService)
  12.        reference = UploadService(mock_removal_service)
  13.        # call upload_complete, call `rm`:
  14.        reference.upload_complete("my uploaded file")
  15.        # test that it called the rm method
  16.        mock_removal_service.rm.assert_called_with("my uploaded file")

在这个例子中,我们甚至不需要修补任何功能,只需为 RemovalService 类创建一个 auto-spec,然后将实例注入到我们的 UploadService 以验证功能.

mock.create_autospec 方法为类提供了一个同等功能实例.实际上来说,这意味着在使用返回的实例进行交互的时候,如果使用了非法的方式将会引发异常.更具体地说,如果一个方法被调用时的参数数目不正确,将引发一个异常.这对于重构来说是非常重要.当一个库发生变化的时候,中断测试正是所期望的.如果不使用 auto-spec,尽管底层的实现已经被破坏,我们的测试仍然会通过.

陷阱:mock.Mock 和 mock.Magicmock 类

mock 库包含了两个重要的类 mock.Mock[5] 和 mock.MagicMock[6],大多数内部函数都是建立在这两个类之上的.当在选择使用 mock.Mock 实例、mock.Magicmock 实例还是 auto-spec 的时候,通常倾向于选择使用 auto-spec,因为对于未来的变化,它更能保持测试的健全.这是因为 mock.Mock 和 mock.Magicmock 会无视底层的 API,接受所有的方法调用和属性赋值.比如下面这个用例:

  1. class Target(object):
  2.    def apply(value):
  3.        return value
  4. def method(target, value):
  5.    return target.apply(value)

我们可以像下面这样使用 mock.Mock 实例进行测试:

  1. class MethodTestCase(unittest.TestCase):
  2.    def test_method(self):
  3.        target = mock.Mock()
  4.        method(target, "value")
  5.        target.apply.assert_called_with("value")

这个逻辑看似合理,但如果我们修改 Target.apply 方法接受更多参数:

  1. class Target(object):
  2.    def apply(value, are_you_sure):
  3.        if are_you_sure:
  4.            return value
  5.        else:
  6.            return None

重新运行你的测试,你会发现它仍能通过.这是因为它不是针对你的 API 创建的.这就是为什么你总是应该使用create_autospec 方法,并且在使用 @patch和 @patch.object 装饰方法时使用 autospec 参数.

现实例子:模拟 Facebook API 调用

作为这篇文章的结束,我们写一个更加适用的现实例子,一个在介绍中提及的功能:发布消息到 Facebook.我将写一个不错的包装类及其对应的测试用例.

  1. import facebook
  2. class SimpleFacebook(object):
  3.    def __init__(self, oauth_token):
  4.        self.graph = facebook.GraphAPI(oauth_token)
  5.    def post_message(self, message):
  6.        """Posts a message to the Facebook wall."""
  7.        self.graph.put_object("me", "Feed", message=message)

这是我们的测试用例,它可以检查我们发布的消息,而不是真正地发布消息:

  1. import facebook
  2. import simple_facebook
  3. import mock
  4. import unittest
  5. class SimpleFacebookTestCase(unittest.TestCase):
  6.    @mock.patch.object(facebook.GraphAPI, 'put_object', autospec=True)
  7.    def test_post_message(self, mock_put_object):
  8.        sf = simple_facebook.SimpleFacebook("fake oauth token")
  9.        sf.post_message("Hello World!")
  10.        # verify
  11.        mock_put_object.assert_called_with(message="Hello World!")

正如我们所看到的,在 Python 中,通过 mock,我们可以非常容易地动手写一个更加智能的测试用例.

Python Mock 总结

即使对它的使用还有点不太熟悉,对单元测试[7]来说,Python的mock库可以说是一个规则改变者.我们已经演示了常见的用例来了解了mock在单元测试中的使用,希望这篇文章能够帮助Python开发者[8]克服初期的障碍,写出优秀、经受过考验的代码.


via:https://www.toptal.com/python/an-introduction-to-mocking-in-python

本文由LCTT[12]原创翻译,Linux中国[13]荣誉推出


[1]: http://www.python.org/dev/peps/pep-0417/
[2]: https://pypi.python.org/pypi/mock
[3]: http://www.voidspace.org.uk/python/mock/patch.html#nesting-patch-decorators
[4]: http://docs.python.org/2/reference/compound_stmts.html#function-deFinitions
[5]: http://www.voidspace.org.uk/python/mock/mock.html
[6]: http://www.voidspace.org.uk/python/mock/magicmock.html#magic-mock
[7]: http://www.toptal.com/qa/how-to-write-testable-code-and-why-it-matters
[8]: http://www.toptal.com/python
[9]: http://www.slviki.com/
[10]: https://github.com/cposture
[11]: https://github.com/wxy
[12]: https://github.com/LCTT/TranslateProject
[13]: https://linux.cn/

推荐阅读

python配置文件操作

python集合类型实例

初入运维的小伙伴,别再问需不需要学Python了

Python单元测试

Python单元测试

Are you ready?

↓↓↓

今天的课程为《 Python单元测试》,内容共分为三个部分:单元测试的概念、工具与方法、Coverage 统计单元测试覆盖率的工具和Mock 简化单元测试的工具。

单元测试的概念、工具与方法

单元测试的概念

image.png

测试具有许多种不同的类型,比如说单元测试、模块测试、联调测试、系统测试、交付测试等。在这些测试之中,单元测试是最先要完成的。单元测试通常是由开发者去完成,用来验证代码中的函数是否符合预期。因此,它聚焦于函数的逻辑以及核心的算法是否正确。通常而言,一个单元测试用例是用于判断在某个特定条件或场景下,某个特定函数的行为。

单元测试的意义

单元测试的意义包括两个方面。

(1)质量

①单元测试主要针对函数,颗粒度小、测试针对性强,bug更容易暴露;

②由于单元测试覆盖面较窄,无需考虑其它函数或者所依赖的模块,所以它的场景易构造,核心功能验证更充分;

③进行单元测试保证整体代码结构良好,使代码就具有较高的可测性和可维护性。

(2)效率

单元测试能够提高开发效率,主要表现在:

①单元测试进行的时间较早,测试场景构建快,可有效减少调试时间。

②由于单元测试只针对修改的代码展开测试,无需考虑额外内容,所以在较短时间内即可把预期的逻辑测试充分。

③单元测试能够在项目开发初期发现的bug,bug发现的时间越早,所带来的收益越大。由于尽早发现bug能够节省整个项目开发的时间,所以单元测试可加快开发效率,缩短开发周期。

单元测试框架

(1)测试框架选择

image.png

在进行单元测试的时候,我们需要测试框架作为工具。一个良好的测试框架能够让测试工作事半功倍,此处我们介绍三个框架。

①Unittest是Python标准库中原生自带的,它的好处在于无兼容性问题,是其他框架的基础;不足之处在于编码略显繁琐,入门门槛稍高。

②Pytest的好处在于编码简洁、生态好、插件丰富、使用人数多;不足之处在于他属于第三方库,使用前另需提前安装。

③Nose2能够兼容Unittest,也属于第三方库,需要安装,但是与Pytest相比,它迭代缓慢,使用人数少。

在这里我们选择Unittest作为单元测试的框架,原因有二:首先,作为Python标准库中原生自带的框架,Unittest无兼容性问题;其次,第三方库难以保证长期快速迭代,易过时。

(2)Unitest的基础概念

在做单元测试之前,需要先了解一下Unittest的几个基础概念。

①Test(测试用例),针对一个特定场景,特定目的具体测试过程。

比如说一个函数通过一组输入测试它,就是一个测试用例;如果一个函数通过三组输入来测试,即为三个测试用例。

②TestCase(测试类),可以包含同一个测试对象的多个测试用例。

如果一个函数通过三组输入来测试,也就是三个测试用例,这三个测试用例可以合成为一个测试类。

③TestSuite(测试集),可以包含多个测试类的多个测试用例。

④Assertion(断言),必须使用断言判断测试结果。

⑤TestFixture,为测试做统一的准备和清除工作,通常是初始化,连接数据库,准备数据,断开数据库,清除现场等。

扩展来说,TestFixture有四种最常使用的作用范围,分别为:

image.png
→ setUp:在测试类的每个测试用例执行前执行。

→ teardown:在测试类的每个测试用例执行后执行。

→ setUpClass:在测试类的第一个测试用例执行前执行。

→ tearDownClass:在测试类的最后一个测试用例执行后执行。

TestFixture可以让单元测试代码更简单,但并非必须使用,也不要求配对出现。

单元测试的规范

想要做好单元测试,必须遵循一定的规范。下面介绍一下单元测试涉及的规范。

(1)所有的单元测试必须使用断言(assert)判断结果,禁止出现无断言的测试用例;

image.png

使用断言,不但有利于他人理解,而且一旦出现不符合预期的情况,可以立即找出问题。

可以使用assertEqual, assertNotEqual 来判断相等或不相等,assertTrue,assertFalse 来判断Boolean, assertRaises 判断抛出的异常是否符合预期。

(2)测试用例需要具有自表述能力,达到见名知意。

比如命名test_login_with_invalid_password(),通过它的名字便可知它是用一个非法的密码去测试登录功能,具有自表述能力;但是如果命名为 test_login_case_(),名字减少了很多信息,难以得知它具体在做什么,不具有自表述能力。

(3)测试用例之间相互独立,不应相互依赖、相互调用。

(4)一个测试用例只测一个函数。一个测试用例里面可以包含这一个函数的多个场景,但不能包含有多个参数的函数。原因在于,复杂测试用例出现错误时,无法定位问题的出处。

单元测试对编码的要求

单元测试中代码需保持一致性,尽量不要出现结果不一致的情况。假设有的代码会带来不一致性,导致单元测试无法稳定运行。针对这种情况,有两种解决方案:第一,将带来不一致性的代码抽取出来,把它作为一种变量传入我们需要调用或使用一致性变量的时候;第二,借助第三部分即将讲到的一个工具——mock——来解决这种问题。

Coverage 统计

单元测试覆盖率的工具

单元测试做完之后如何评价我们单元测试的效果。此时需要用到覆盖率工具,即Coverage。Coverage是一个第三方的工具,需要提前下载安装。

(1)统计覆盖率方法

把python替换为coverage run-branch,然后会生成coverage文件,文件里会记录所有我们需要的覆盖率信息。

(2)打印覆盖率信息

image.png
执行coverage report-m 命令,读取当前目录下.coverage文件,打印覆盖率信息。输出Stmts(总行数), Miss(未覆盖行数), Branch(总分支数), BrPart (未覆盖分支数), Cover(覆盖率) , Missing(未覆盖具体信息)等信息。

(3)覆盖率中排除某些文件

执行coverage report-m—omit=file 1[,file 2,……] 命令, 在统计并打印覆盖率时,排除某些文件。若有多个文件用逗号分隔。

(4)生成HTML格式的覆盖率信息

针对代码量较大,查找覆盖率信息难度较大、耗时较长的情况,执行coverage html [--omit=file1[,file2,……]]命令,将覆盖率信息以html格式显示。

Mock 简化单元测试的工具

使用mock工具的原因与其功能

Mock基于实际进行单元测试的场景而产生,以下三类场景非常具有代表性:

①构造模块。需要测试模块A,但它要调用的模块B还未开发,可是测试却不容推迟、需按时进行,面对这种情况,我们可以使用Mock生成一个还未写完的代码,即可进行相应的测试。

②改变函数逻辑。代码中含有结果不可预知的代码,例如time.time()(时间), random.random()(随机数)。Mock可以改变含有结果不可预知代码的函数的逻辑,强行让其返回我们想要的返回值,使其结果可预知。

③减少依赖。在所有模块代码都已完成,但无法保证代码稳定性的情况下。针对其他模块的质量不可靠的情况,可通过Mock工具构造一个相对稳定的模块,从而规避其他模块的问题。

Mock使用场景

通过以下10个场景来讲述Mock的常见用法。

场景01:通过 return_value,Mock可以强行修改,永远返回我们想要的返回值,支持的类型包括string,number,Boolean,list,dict等。

场景02:将前一个例子的实例名改为类名,可实现替换类方法的返回值。

场景03:通过 side_effect,根据调用次数返回想要的结果,当超出调用次数时抛StopIteration 异常。

场景04:通过 side_effect可以完全修改函数的逻辑,使用另一个函数来替换它,根据参数返回想要的结果。

场景05:通过 side_effect抛出想要的异常或错误。

场景06:针对需要mock在特定要求下生效的情况,通过with.patch.object设定一个作用域以达到限制mock作用域的目的。

场景07:获取调用信息,如函数是否被调用、函数被调用的次数、函数被调用的形式、函数调用的参数等。

场景08:通过create_autospec在返回值改变的同时,确保api不会因mock而改变。

场景09:针对需要调用的函数、调用的接口完全没有开发的情况,可以通过Mock从零构造依赖模块从而完成测试。

场景10 :替换函数调用链。比如说用popen去执行一个命令,然后用read函数把它读取出来,再用split去做切分,这就是一个函数调用链(os.popen(cmd).read().split())。

Mock 对编码的要求

在模块引入方式上,推荐以import XXX的形式引入,以XXX.func()形式调用,不要from.xxx import *,因为需要一个链条指向它,否则无法达到我们的预期。

关于Python单元测试

我们今天就讲解到这啦
点击进入获得更多技术信息~~

python单元测试---unittest

python单元测试---unittest

单元测试,集成测试,功能测试

单元测试

 颗粒度最小,一般由开发小组采用白盒方式来测试,主要测试单元是否符合“设计”;是指对软件中的最小可测试单元进行检查和验证

集成测试

介于单元测试和系统测试之间,一般由开发小组采用白盒+黑盒的方法来测试,即验证“设计”又验证“需求”。主要用来测试模板与模板之间的接口,同时还要测试一些主要的业务功能。

功能测试

  颗粒度最大,一般由独立的测试小组采用黑盒的方式来测试,主要测试系统是否符合“需求规格说明书

白盒测试

主要应用于单元测试阶段,主要是对代码级别的测试,针对程序内部的逻辑结构。测试的手段有:语句覆盖、判定覆盖、条件覆盖、路径覆盖和条件组合覆盖

黑盒测试

不考虑程序内部结构和逻辑结构,主要是测试系统的功能是否满足“需求规格说明书”。一般会有一个输入值和一个输出值,和期望值做比较

Unittest重要组成

Python中有一个自带的单元测试框架是unittest模块,用它来做单元测试,它里面封装好了一些校验返回的结果方法(断言)和一些用例执行前的初始化操作。

unittest核心部分:TestFixture、TestCase、TestSuite、TestRunner

TestFixture

作用:用于一个测试环境的准备和销毁还原

主要方法:

setUp():准备环境,执行每个测试用例的前置条件;
tearDown():环境还原,执行每个测试用例的后置条件;
setUpClass():必须使用@classmethod装饰器,所有case执行的前置条件,只运行一次;
tearDownClass():必须使用@classmethod装饰器,所有case运行完后只运行一次;

TestCase:测试用例

定义:

一个类class继承 unittest.TestCase,就是一个测试用例--------一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。

测试用例命名规则:

继承自unittest.TestCase的类中,测试方法的名称要以test开头。且只会执行以test开头定义的方法(测试方法),测试用例执行的顺序会按照方法名的ASCII值排序。
如果想跳过某个测试用例,需要添加@unittest.skip)(‘描述信息')

TestSuite

测试套件,可以将多个测试用例集合在一起,能一起执行选中的测试用例

#方式一:
suite = unittest.TestSuite()#创建测试套件
case_list = [“test1”,”test2”….]
For case in case_list:
    suite.addTest(类名(case))
#方式二:
suite = unittest.TestSuite()#创建测试套件
        suite.addTest(类名 (“test1“))
        suite.addTest(类名 (“test2“))
#方式三:
suite = unittest.TestSuite()#创建测试套件
loader = unittest.TestLoader()# 创建一个加载对象 
suite .addTest(loader.loadTestsFromTestCase(类名))

TextRunner

执行测试用例

通过TextTestRunner类提供的run()方法来执行test suite/test case

runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
verbosity :表示测试报告信息的详细程度,一共三个值,默认是 2
0 ( 静默模式 ) :你只能获得总的测试用例数和总的结果,如:总共 100 个 失败 10 成功 90
1 ( 默认模式 ) :类似静默模式,只是在每个成功的用例前面有个 . 每个失败的用例前面有个 F
2 ( 详细模式 ) :测试结果会显示每个测试用例的所有相关的信息

 断言

验证预期结果和实际结果

assertEqual(a,b):断言a和b是否相等,相等则测试用例通过。
assertNotEqual(a,b):断言a和b是否相等,不相等则测试用例通过。
assertTrue(x):断言x是否True,是True则测试用例通过。
assertFalse(x):断言x是否False,是False则测试用例通过。
assertIs(a,b):断言a是否是b,是则测试用例通过。
assertNotIs(a,b):断言a是否是b,不是则测试用例通过。
assertIn(a,b):断言a是否在b中,在b中则测试用例通过。
assertnotin(a,b):断言a是否在b中,不在b中则测试用例通过。

生成测试报告

html格式的就是HTMLTestRunner了,HTMLTestRunner是 Python 标准库的 unittest 框架的一个扩展,它可以生成一个直观清晰的 HTML 测试报告。使用的前提就是要下载 HTMLTestRunner.py

with open("../report.html","wb") as f:
    HTMLTestRunner(
        stream=f,
        title="单元测试",
        description="测试一期",
        verbosity=2
            ).run(suite)    
#stream:指定输出的方式
#description:报告中要显示的面熟信息
#title:测试报告的标题
#verbosity :表示测试报告信息的详细程度,一共三个值,默认是2
#0 (静默模式):你只能获得总的测试用例数和总的结果,如:总共100个 失败10 成功90
#1 (默认模式):类似静默模式,只是在每个成功的用例前面有个. 每个失败的用例前面有个F
#2 (详细模式):测试结果会显示每个测试用例的所有相关的信息

 操作

1:导入unittest模块   >>>import unittest
2:编写一个类继承unittest.TestCase
3:调用setUp(self), tearDown(self)方法实现测试用例前后阶段的操作
4:编写测试用例方法
    (1)该方法必须以test开头,否则在unittest.main()中调用测试找不到该方法
    (2)设置断言进行判断,输入数据和输出数据的预期结果
5:创建套件,将多个测试用例存放套件中,一并执行()
6:生成测试报告(python自带或者导入HTMLTestRunner生成html格式的测试报告)
7:运行测试用例unittest.main(),调用测试用例中以test开头的方法

代码展示

对开发代码块进行单元测试

#开发被测代码
class Calc():
    def add(self,a,b):
        c =a+b
        return c
    def redc(self,a,b):
        c = a-b
        print(c)
if __name__ == "__main__":
    c = Calc()
    c.add(2,3)
    print(c.add(2,3))
#单元测试代码
from day.Calc import Calc
import  unittest
c = Calc()  #实例化开发的类
class Test(unittest.TestCase):#unittest单元测试类必须继承unittest.TestCase
    def setUp(self): #测试用例之前执行
        print("start")
    def test001(self):#测试用例必须test开头
        res = c.add(2,1)   #调取相加的方法
        self.assertEqual (res,5)  #断言,预期结果和实际结果的对比
    def test002(self):#测试用例必须test开头
        res = c.redc(2,3)   #调取相减的方法
        self.assertEqual (res,-1)  #断言,预期结果和实际结果的对比
    def tearDown(self):#测试用例结束之后执行
        print("end")
if __name__ == '__main__':
    unittest.main()

上面单元测试没有实现代码与数据分离 所以需要改进

读取文件

读取xml文件

from xml.dom import minidom
class readxml():
    def read_xml(self,filename,onename,twoname):
        root =minidom.parse(filename)
        firstnode =root.getElementsByTagName(onename)[0]
        secondnode=firstnode.getElementsByTagName(twoname)[0].firstChild.data
        return secondnode

读取csv文件

import csv   #导入csv模块
class ReadCsv():
    def read_csv(self):
        item =[]    #定义一个空列表
        c = csv.reader(open("../commonDemo/test1.csv","r"))    #得到csv文件对象
        for csv_i in c:
            item.append(csv_i)      #将获取的数据添加到列表中
        return item
            
r = ReadCsv()
print(r.read_csv())

总结:

如何做单元测试:
    创建一个data包,存放测试数据,使业务代码和测试数据进行分离
    创建一个readdata包,读取测试数据
    创建用例包,导入unittest模块,开发代码块,读取数据代码块
        测试用例模块,定义一个类继承unittest.TestCase
        测试用例必须以test开头
        在测试用例中添加断言,验证读取的数据的预期结果和开发代码中返回的实际结果
        接着导入htmltestrunner.py模块,生成测试报告
        最后在main方法中通过unittest.main()执行测试用例即可

 

我们今天的关于断言在Python单元测试中已调用方法python unittest 断言的分享就到这里,谢谢您的阅读,如果想了解更多关于Django中的单元测试以及Python单元测试、Mock在Python单元测试中的使用、Python单元测试、python单元测试---unittest的相关信息,可以在本站进行搜索。

本文标签: