您的位置:首页 > 编程语言 > Python开发

python 单元测试的模块 pyUnit(unittest),mock与Python测试

2017-12-08 16:44 786 查看

pyUnit(unittest)

如何调用unittest模块进行单元测试

1.import unittest
2.定义一个继承自unittest.TestCase的测试用例类
3.定义setUp和tearDown,在每个测试用例前后做一些辅助工作。
4.定义测试用例,名字以test开头。
5.一个测试用例应该只测试一个方面,测试目的和测试内容应很明确。主要是调用assertEqual、assertRaises等断言方法判断程序执行结果和预期值是否相符。
6.调用unittest.main()启动测试
7.如果测试未通过,会输出相应的错误提示。如果测试全部通过则不显示任何东西,这时可以添加-v参数显示详细信息。


unittest模块常用方法:

assertEqual(a, b)     a == b
assertNotEqual(a, b)     a != b
assertTrue(x)     bool(x) is True
assertFalse(x)     bool(x) is False
assertIs(a, b)     a is b     2.7
assertIsNot(a, b)     a is not b     2.7
assertIsNone(x)     x is None     2.7
assertIsNotNone(x)     x is not None     2.7
assertIn(a, b)     a in b     2.7
assertNotIn(a, b)     a not in b     2.7
assertIsInstance(a, b)     isinstance(a, b)     2.7
assertNotIsInstance(a, b)     not isinstance(a, b)     2.7


下面我们以一个例子来测试网站的连通性(python3):

import unittest
import urllib.request

class TestUrlHttpcode(unittest.TestCase):
def setUp(self):
urlinfo = ['https://www.lockey.io','http://www.baidu.com','http://www.163.com','http://www.sohu.com','http://www.cnpythoner.com']
self.checkurl = urlinfo

def test_ok(self):
for m in self.checkurl:
httpcode = urllib.request.urlopen(m).getcode()
self.assertEqual(httpcode,200)

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


注意以上例子对应python2.x要做修改的,因为urllib在Python2.x和python3.x中是不一样的

在python2.x中应该这样写:

httpcode = urllib.urlopen(m).getcode()


否则会报错:

AttributeError: module 'urllib' has no attribute 'urlopen'


运行以上例子,因为有一个网站不存在所以返回结果有一个错误的:

ERROR: test_ok (__main__.TestUrlHttpcode)
----------------------------------------------------------------------
Traceback (most recent call last):

File "/usr/local/lib/python3.5/http/client.py", line 849, in connect
(self.host,self.port), self.timeout, self.source_address)

socket.gaierror: [Errno -2] Name or service not known

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "tt1.py", line 11, in test_ok
httpcode = urllib.request.urlopen(m).getcode()

File "/usr/local/lib/python3.5/urllib/request.py", line 1256, in do_open
raise URLError(err)
urllib.error.URLError: <urlopen error [Errno -2] Name or service not known>

----------------------------------------------------------------------
Ran 1 test in 0.019s

FAILED (errors=1)


mock

mock是一门技术,通过伪造部分实际代码,从而让我们能够验证剩余代码的正确性。

当我们进行单元测试的时候,我们的目标往往是为了测试非常小的代码块,例如一个独立存在的函数或类方法。换句话说,我们只需要针对那个函数内部的代码进行测试。如果测试代码依赖于其他的代码片段,即使被测试的函数没有变化,我们会发现在某种不幸的情形下,这部分内嵌代码的修改可能会破坏原有的测试。

要解决以上问题有两种方案。我们要么忽略它,像集成测试那样去进行单元测试,要么求助于 mock。第一种方案的缺点是,集成测试仅仅告诉我们函数调用时哪一行代码出问题了,这样更难找到问题根源所在。这并不是说,集成测试没有用处,因为在某些情况下它确实非常有用。不管怎样,单元测试和集成测试用于解决不同的问题,它们应该被同时使用。因此,如果我们想要成为一个好的测试人员,我们会选择另一种方案:mock。

mock 有多种不同的用法。我们可以用它提供猴子补丁功能,创建伪造的对象,甚至可以作为一个上下文管理器。所有这些都是基于一个共同目标的,用副本替换部分代码来收集信息并返回伪造的响应。

如我们有以下一个问题:

我们想使用单元测试来验证两个数是否相等,一个数给定,另一个数由一个外部函数的计算结果影响,那么我们怎么使得外部函数不影响单元测试呢?请看代码:

一下为我们的外部函数文件outer.py

def add_and_multiply(x, y):

addition = x + y
multiple = multiply(x, y)

return (addition, multiple)

def multiply(x, y):

return x * y + 3


我们有两个测试文件,一个使用了mock如下:

import mock
import unittest
from outer import add_and_multiply

class MyTestCase(unittest.TestCase):

@mock.patch('outer.multiply')
def test_add_and_multiply(self, mock_multiply):

x = 3
y = 5

mock_multiply.return_value = 15

addition, multiple = add_and_multiply(x, y)

mock_multiply.assert_called_once_with(3, 5)

self.assertEqual(8, addition)
self.assertEqual(15, multiple)

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


另一个未使用mock:

import unittest
from outer import add_and_multiply

class MyTestCase(unittest.TestCase):
def test_add_and_multiply(self):

x = 3
y = 5

addition, multiple = add_and_multiply(x, y)

self.assertEqual(8, addition)
self.assertEqual(15, multiple)

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


运行结果如下:



解释一下上面的过程做了什么:

我们使用mock.patch装饰器来用 mock 对象替换multiply。然后,我们将它作为一个参数mock_multiply插入到我们的测试代码中。在这个测试的上下文中,任何对multiply的调用都会被重定向到mock_multiply对象。
通常情况下,当我们调用multiply(),我们实际执行的是multiply函数的__call__方法。然而,恰当的使用 mock,对multiply()的调用将执行我们的mock对象而不是__call__方法。

mock_multiply.return_value = 15

为了使 mock 函数可以返回任何东西,我们需要定义其return_value属性。实际上,当 mock 函数被调用时,它用于定义 mock 对象的返回值。

在测试代码中,我们调用了外部函数add_and_multiply。它会调用内嵌的multiply函数,如果我们正确的进行了 mock,调用将会被我们定义的 mock 对象取代。为了验证这一点,我们可以用到 mock 对象的高级特性 - 当它们被调用时,传给它们的任何参数将被储存起来。顾名思义,mock 对assert_called_once_with方法就是一个不错的捷径来验证某个对象是否被一组特定的参数调用过。如果被调用了,测试通过。反之,assert_called_once_with会抛出AssertionError的异常。


上面的例子是对单元测试的很好的应用:

首先,我们通过 mock 将multiply函数从add_and_multiply中分离出来。这就意味着我们的单元测试只针对add_and_multiply的内部逻辑。只有针对add_and_multiply的代码修改将影响测试的成功与否。

其次,我们现在可以控制内嵌函数的输出,以确保外部函数处理了不同的情况。例如,add_and_multiply可能有逻辑条件依赖于multiply的返回值:比如说,我们只想在乘积大于 10 的条件下返回一个值。通过人为设定multiply的返回值,我们可以模拟乘积小于 10 的情况以及乘积大于 10 的情况,从而可以很容易测试我们的逻辑正确性。

最后,我们现在可以验证被 mock 的函数被调用的次数,并传入了正确的参数。由于我们的 mock 对象取代了multiply函数的位置,我们知道任何针对multiply函数的调用都会被重定向到该 mock 对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: