资讯专栏INFORMATION COLUMN

Python中的单元测试

Heier / 1645人阅读

摘要:每个测试方法的名称以单词开头,单元测试是如何识别它们是测试的。它还意味着知道测试文件中有多少单元测试,而不是简单地知道有多少表达式您可能已经注意到将每个行作为多带带的测试计数。

来源 | 愿码(ChainDesk.CN)内容编辑

愿码Slogan | 连接每个程序员的故事

网站 | http://chaindesk.cn

愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。

官方公众号 | 愿码 | 愿码服务号 | 区块链部落

免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码


本文阅读时长:11min

基本单元测试

在我们开始讨论新的概念和功能之前,让我们来看看如何使用unittest来表达我们已经学到的想法。这样,我们就能有一些坚实的基础来建立我们的新理解。

采取行动的时间-用unittest测试PID

我们将访问PID类(或至少访问PID类的测试)。我们将编写测试,以便它们在unittest框架内运行。

我们将使用unittest框架实现测试。

创建一个名为新文件test_pid.py在同一目录pid.py。请注意,这是一个.py文件:unittest测试是纯 python源代码,而不是包含源代码的纯文本。这意味着从纪录片的角度来看,测试的用处不大,但可以交换其他好处。

将以下代码插入到新创建的test_pid.py中

from unittest import TestCase, main
from mocker import Mocker
import pid
class test_pid_constructor(TestCase):
 def test_without_when(self):
 mocker = Mocker()
 mock_time = mocker.replace("time.time")
 mock_time()
 mocker.result(1.0)
 mocker.replay()
 controller = pid.PID(P=0.5, I=0.5, D=0.5,
 setpoint=0, initial=12)
 mocker.restore()
 mocker.verify()
 self.assertEqual(controller.gains, (0.5, 0.5, 0.5))
 self.assertAlmostEqual(controller.setpoint[0], 0.0)
 self.assertEqual(len(controller.setpoint), 1)
 self.assertAlmostEqual(controller.previous_time, 1.0)
 self.assertAlmostEqual(controller.previous_error, -12.0)
 self.assertAlmostEqual(controller.integrated_error, 0)
 def test_with_when(self):
 controller = pid.PID(P=0.5, I=0.5, D=0.5,
 setpoint=1, initial=12,
 when=43)
 self.assertEqual(controller.gains, (0.5, 0.5, 0.5))
 self.assertAlmostEqual(controller.setpoint[0], 1.0)
 self.assertEqual(len(controller.setpoint), 1)
 self.assertAlmostEqual(controller.previous_time, 43.0)
 self.assertAlmostEqual(controller.previous_error, -11.0)
 self.assertAlmostEqual(controller.integrated_error, 0)
class test_calculate_response(TestCase):
 def test_without_when(self):
 mocker = Mocker()
 mock_time = mocker.replace("time.time")
 mock_time()
 mocker.result(1.0)
 mock_time()
 mocker.result(2.0)
 mock_time()
 mocker.result(3.0)
 mock_time()
 mocker.result(4.0)
 mock_time()
 mocker.result(5.0)
 mocker.replay()
 controller = pid.PID(P=0.5, I=0.5, D=0.5,
 setpoint=0, initial=12)
 self.assertEqual(controller.calculate_response(6), -3)
 self.assertEqual(controller.calculate_response(3), -4.5)
 self.assertEqual(controller.calculate_response(-1.5), -0.75)
 self.assertEqual(controller.calculate_response(‑2.25), 
‑1.125)
 mocker.restore()
 mocker.verify()
 def test_with_when(self):
 controller = pid.PID(P=0.5, I=0.5, D=0.5,
 setpoint=0, initial=12,
 when=1)
 self.assertEqual(controller.calculate_response(6, 2), -3)
 self.assertEqual(controller.calculate_response(3, 3), -4.5)
 self.assertEqual(controller.calculate_response(‑1.5, 4), 
‑0.75)
 self.assertEqual(controller.calculate_response(‑2.25, 5), 
‑1.125)
if __name__ == "__main__":
 main()

键入以下命令运行测试:$ python test_pid.py

让我们浏览代码部分,看看每个部分的作用。

from unittest import TestCase, main
from mocker import Mocker
import pid
class test_pid_constructor(TestCase):
 def test_without_when(self):
 mocker = Mocker()
 mock_time = mocker.replace("time.time")
 mock_time()
 mocker.result(1.0)
 mocker.replay()
 controller = pid.PID(P=0.5, I=0.5, D=0.5,
 setpoint=0, initial=12)
 mocker.restore()
 mocker.verify()
 self.assertEqual(controller.gains, (0.5, 0.5, 0.5))
 self.assertAlmostEqual(controller.setpoint[0], 0.0)
 self.assertEqual(len(controller.setpoint), 1)
 self.assertAlmostEqual(controller.previous_time, 1.0)
 self.assertAlmostEqual(controller.previous_error, -12.0)
 self.assertAlmostEqual(controller.integrated_error, 0)

在一些设置代码之后,我们进行了测试,当没有给出when参数时,PID控制器正常工作。Mocker用于将time.time替换为始终返回可预测值的模拟,然后我们使用多个断言来确认控制器的属性已初始化为预期值。

def test_with_when(self):
 controller = pid.PID(P=0.5, I=0.5, D=0.5,
 setpoint=1, initial=12,
 when=43)
 self.assertEqual(controller.gains, (0.5, 0.5, 0.5))
 self.assertAlmostEqual(controller.setpoint[0], 1.0)
 self.assertEqual(len(controller.setpoint), 1)
 self.assertAlmostEqual(controller.previous_time, 43.0)
 self.assertAlmostEqual(controller.previous_error, -11.0)
 self.assertAlmostEqual(controller.integrated_error, 0)

此测试确认在提供when参数时PID构造函数正常工作。与之前的测试不同,不需要使用Mocker,因为测试的结果不应该依赖于除参数值之外的任何东西 - 当前时间是无关紧要的。

class test_calculate_response(TestCase):
 def test_without_when(self):
 mocker = Mocker()
 mock_time = mocker.replace("time.time")
 mock_time()
 mocker.result(1.0)
 mock_time()
 mocker.result(2.0)
 mock_time()
 mocker.result(3.0)
 mock_time()
 mocker.result(4.0)
 mock_time()
 mocker.result(5.0)
 mocker.replay()
 controller = pid.PID(P=0.5, I=0.5, D=0.5,
 setpoint=0, initial=12)
 self.assertEqual(controller.calculate_response(6), -3)
 self.assertEqual(controller.calculate_response(3), -4.5)
 self.assertEqual(controller.calculate_response(-1.5), -0.75)
 sel+f.assertEqual(controller.calculate_response(‑2.25), 
‑1.125)
 mocker.restore()
 mocker.verify()

此类中的测试描述了calculate_response方法的预期行为。第一个测试检查未提供可选的when参数时的行为,并模拟time.time以使该行为可预测。

def test_with_when(self):
 controller = pid.PID(P=0.5, I=0.5, D=0.5,
 setpoint=0, initial=12,
 when=1)
 self.assertEqual(controller.calculate_response(6, 2), -3)
 self.assertEqual(controller.calculate_response(3, 3), -4.5)
 self.assertEqual(controller.calculate_response(‑1.5, 4), 
‑0.75)
 self.assertEqual(controller.calculate_response(‑2.25, 5), 
‑1.125)

在此测试中,提供了when参数,因此无需模拟time.time。我们只需检查结果是否符合预期。

我们执行的实际测试与doctest中编写的测试相同。到目前为止,我们所看到的只是一种表达它们的不同方式。

首先要注意的是,测试文件被划分为继承自unittest.TestCase的类,每个类都包含一个或多个测试方法。每个测试方法的名称以单词test开头,单元测试是如何识别它们是测试的。

每种测试方法都包含对单个单元的单个测试。这为我们提供了一种方便的方法来构建我们的测试,将相关测试组合到同一个类中,以便更容易找到它们。

将每个测试放入自己的方法意味着每个测试都在一个独立的命名空间中执行,这使得相对于doctest风格的测试,使得单元测试式测试更容易相互干扰。它还意味着unittest知道测试文件中有多少单元测试,而不是简单地知道有多少表达式(您可能已经注意到doctest将每个>>>行作为多带带的测试计数)。最后,将每个测试放在自己的方法中意味着每个测试都有一个名称,这可能是一个有价值的功能。

unittest中的测试并不直接关注任何不属于调用TestCase的assert方法的任何内容。这意味着当我们使用Mocker时,我们不必担心从演示表达式返回的模拟对象,除非我们想要使用它们。这也意味着我们需要记住写一个断言来描述我们想要检查的测试的每个方面。我们将很快介绍TestCase的各种断言方法。

如果您无法执行测试,则测试没有多大用处。目前,我们将采用的方式是通过Python解释器将测试文件作为程序执行时 调用unittest.main。这是运行unittest代码的最简单方法,但是当你在很多文件中分布了大量测试时,这很麻烦。

如果__name__ =="__ main__":当 Python加载任何模块时,它将该模块的名称存储在模块中名为__name__的变量中(除非该模块是在命令行上传递给解释器的模块)。该模块始终将字符串"__main__"绑定到其__name__变量。因此,如果__name__ =="__ main__":表示 - 如果此模块直接从命令行执行。

Assertions

Assertions是我们用来告诉unittest测试的重要结果是什么的机制。通过使用适当的断言,我们可以准确地告诉unittest每次测试的期望。

assertTrue

当我们调用self.assertTrue(expression)时,我们告诉unittest表达式必须为true才能使测试成功。

这是一个非常灵活的断言,因为您可以通过编写适当的布尔表达式来检查几乎任何内容。这也是你应该考虑使用的最后一个断言之一,因为它没有告诉unittest你正在进行的比较的类型,这意味着unittest无法清楚地告诉你如果测试失败会出现什么问题。

有关此示例,请考虑以下测试代码,其中包含两个保证失败的测试:

from unittest import TestCase, main
class two_failing_tests(TestCase):
 def test_assertTrue(self):
 self.assertTrue(1 == 1 + 1)
 def test_assertEqual(self):
 self.assertEqual(1, 1 + 1)
if __name__ == "__main__":
 main()

看起来两个测试似乎是可以互换的,因为两个测试都是相同的。当然他们都会失败(或者在不太可能的情况下,他们都会失败),所以为什么选择一个而不是另一个呢?

看看我们运行测试时会发生什么(并且还注意到测试没有按照它们编写的顺序执行;测试完全相互独立,所以没关系,对吧?):

你看得到差别吗?该assertTrue测试能够正确地确定测试失败,但它不知道够报告关于失败原因的任何有用的信息。该assertEqual便测试,而另一方面,他知道首先,它是检查两个表达式是相等的,其次它知道如何呈现的结果,因此,他们将是最有用的:通过评估各个它是表达的比较并在结果之间放置一个!=符号。它告诉我们什么期望失败,以及相关表达式评估的内容。

assertFalse

assertFalse方法会成功时assertTrue方法会失败,反之亦然。它在产生assertTrue所具有的有用输出方面具有相同的限制,并且在能够测试几乎任何条件方面具有相同的灵活性。

assertEqual

正如assertTrue讨论中所提到的,assertEqual断言检查它的两个参数实际上是相等的,并且如果它们不是,则报告失败,以及参数的实际值。

assertNotEqual

assertNotEqual每当断言失败assertEqual便断言会成功,反之亦然。报告失败时,其输出表明两个表达式的值相等,并为您提供这些值。

assertAlmostEqual

正如我们之前看到的,比较浮点数可能很麻烦。特别是,检查两个浮点数是否相等是有问题的,因为你可能期望相等的事情 - 在数学上是相等的 - 可能仍然最终在最低有效位之间不同。浮点数仅在每个位相同时才相等。

为了解决这个问题,unittest提供了assertAlmostEqual,它检查两个浮点值是否几乎相同; 它们之间的少量差异是可以容忍的。

让我们看一下这个问题。如果取平方根7,然后将其平方,则结果应为7.这是一对检查该事实的测试:

from unittest import TestCase, main
class floating_point_problems(TestCase):
 def test_assertEqual(self):
 self.assertEqual((7.0 ** 0.5) ** 2.0, 7.0)
def test_assertAlmostEqual(self): 
 self.assertAlmostEqual((7.0 ** 0.5) ** 2.0, 7.0) 
if __name__ == "__main__": 
 main()  

test_assertEqual方法检查

这在现实中是如此。然而,在计算机可用的更专业的数字系统中,取7的平方根然后平方它并不能让我们回到7,所以这个测试将失败。稍等一下。

测试test_assertAlmostEqual方法检查

即使计算机会同意这是真的,所以这个测试应该通过。

运行这些测试会产生以下结果,尽管您返回的具体数字可能会有所不同,具体取决于运行测试的计算机的详细信息:

不幸的是,浮点数不精确,因为实数行上的大多数数字不能用有限的,非重复的数字序列表示,更不用说仅仅64位。因此,你从评估数学表达式得到的回报并不是很好。虽然 - 或者几乎任何其他类型的工作都足够接近政府工作 - 所以我们不希望我们的测试对这个微小的差异进行狡辩。因此,当我们比较浮点数是否相等时,我们应该使用assertAlmostEqualassertNotAlmostEqual

这个问题通常不会延续到其他比较运算符中。例如,检查一个浮点数小于另一个,由于无意义的错误,不太可能产生错误的结果。只有在平等的情况下,这个问题才会困扰我们。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/43760.html

相关文章

  • 说说Python中的单元测试

    摘要:必然的,他们会抛弃标准库中的,使用或者发明自己心仪的单元测试框架。究其原因,一些人会说时间写代码都不够,哪还有空写单元测试。最后我的个人观点,单元测试其实还有一个非常重要的作用,就是替代函数文档注释。希望从今天起,你的代码也都有单元测试。 单元测试是每种编程语言必学的课题,是保护开发者的强力护盾,每个程序员都在时间允许的情况下尽可能多的写单元测试,今天我们不讨论其必要性,只抛砖引玉聊一...

    chengjianhua 评论0 收藏0
  • 通过demo学习OpenStack开发所需的基础知识 -- 单元测试

    摘要:本文将进入单元测试的部分,这也是基础知识中最后一个大块。本文将重点讲述和中的单元测试的生态环境。另外,在中指定要运行的单元测试用例的完整语法是。中使用模块管理单元测试用例。每个项目的单元测试代码结构可 本文将进入单元测试的部分,这也是基础知识中最后一个大块。本文将重点讲述Python和OpenStack中的单元测试的生态环境。 单元测试的重要性 github上有个人画了一些不同语言的学...

    douzifly 评论0 收藏0
  • python学习笔记- 单元测试,UnitTest

    摘要:所谓的单元测试,就是对一个模块,一个函数,或则是一个类进行正确性检测的一类测试工作。当然,单元测试也会让代码量大大增加。编写单元测试代码需要引入的包。再所有单元测试开始前运行函数在所有单元测试运行后运行。 所谓的单元测试,就是对一个模块,一个函数,或则是一个类进行正确性检测的一类测试工作。 以测试驱动的开发方式叫做测试驱动开发(Test Drived Development). 这种开...

    k00baa 评论0 收藏0
  • 2021年软件测试工具总结——单元测试工具

    摘要:单元测试框架作为的标准库,是其他单元测试框架的基础。可以和和配合使用编写单元测试。官网地址单元测试覆盖率工具单元测试中还需要用到代码覆盖率工具。代码覆盖率统计工具用来发现没有被测试覆盖的代码,完善单元测试的覆盖率。 在应用程序中,单元是具有一个或多个输入和单个输出的软件中最小可测试部分。单元...

    qingshanli1988 评论0 收藏0
  • Python单元测试两种方法解答

      小编这这篇文章的主要目的,主要是给大家进行一个详解,解释一下关于Python中,单元格测试的一些具体方法,那么,测试的方法都有什么呢?下面小编就给大家详细的做出一个解答。  一、前言  python的两个单元测试包分别是doctest和unittest,这两个包的使用起来各有长处,适用于不同的场景  doctest:直接写在方法体中,利用了python动态语言的特性,书写方式简单明了,前提是项...

    89542767 评论0 收藏0
  • 代码测试用例指南

    摘要:测试的通用规则测试单元应该集中于小部分的功能,并且证明它是对的。通过去除依赖尽量使测试单元快速运行。实现来持续集成通过代码提交的本地或者来持续集成测试你的代码。 原文链接:http://blog.speedx.com/backend-test-guide 将测试代码和运行代码一起写是一种非常好的习惯。聪明地使用这种方法将会帮助你更加精确地定义代码的含义,并且代码的耦合性更低。 测试的通...

    chinafgj 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<