Concurrency In Python 简明教程

Testing Thread Applications

在本章中,我们将学习线程应用程序的测试。我们还将学习测试的重要性。

Why to Test?

在我们深入讨论测试重要性之前,我们需要知道什么是测试。一般而言,测试是对某些事物运行良好程度进行检测的一种技术。另一方面,具体而言,如果我们谈论计算机程序或软件,那么测试就是访问软件程序功能的技术。

在本章节中,我们将讨论软件测试的重要性。在软件开发中,在软件发布给客户端之前必须进行仔细检查。这就是由经验丰富的测试团队测试软件非常重要的原因。考虑以下几点来了解软件测试的重要性:

Improvement of software quality

当然,没有哪家公司愿意交付低质量的软件,也没有哪个客户愿意购买低质量的软件。测试通过查找并修复其中的错误来提高软件的质量。

Satisfaction of customers

任何业务最重要的部分就是客户满意度。通过提供无 bug 且质量良好的软件,公司可以实现客户满意度。

Lessen the impact of new features

假设我们已经建立了一个 10000 行的软件系统,我们需要添加一个新功能,那么开发团队就会关注此新功能对整个软件的影响。在这里,测试仍然扮演着至关重要的角色,因为如果测试团队已经构建了一套很好的测试,它可以避免我们发生任何潜在的灾难性中断。

User experience

任何企业最重要的另一个部分就是产品用户的体验。只有测试才能确保最终用户发现使用产品简单易用。

Cutting down the expenses

测试可以通过在其开发阶段找到并修复错误来降低软件的总成本,而不是在交付后进行修复。如果在交付软件后出现重大错误,那么它将在有形成本(即支出成本)和无形成本(即客户不满、公司声誉下降等)方面增加其成本。

What to Test?

强烈建议适当了解要测试的内容。在这一节中,我们首先理解测试人员在测试任何软件时的主要动机。测试时应避免代码覆盖率,即我们的测试套件在测试时命中了多少行代码。这是因为,在测试时,只关注代码行数并不会给我们的系统增加任何实际价值。可能仍会有一些 bug,即使用户在部署后也会在后续阶段反映出来。

考虑与要测试的内容相关的以下重要要点 -

  1. 我们需要专注于测试代码的功能,而不是代码覆盖率。

  2. 我们需要先测试代码中最重要的部分,然后转向不太重要的部分。这绝对能省时。

  3. 测试人员必须拥有多种不同的测试,这些测试能够将软件推到其极限。

Approaches for testing concurrent software programs

由于能够利用多核架构的真正能力,并发软件系统正在取代顺序系统。近来,并发系统程序被广泛应用于从手机到洗衣机,从汽车到飞机等各个领域。我们需要更小心地测试并发软件程序,因为如果我们已向存在 bug 的单线程应用程序添加了多条线程,那么最终会产生多个 bug。

并发软件程序的测试技术主要集中于选择交错,揭示潜在的有害模式,如竞争条件、死锁和原子性违规。以下是测试并发软件程序的两种方法 -

Systematic exploration

这种方法的目标是尽可能广泛地探索交错空间。此类方法可以采用蛮力技术,而另一些方法采用偏序规约技术或启发式技术来探索交错空间。

Property-driven

基于属性的方法依赖于以下观察:并发错误更有可能发生在揭示特定属性的交错下,例如可疑的内存访问模式。不同的基于属性的方法针对不同的故障,例如竞争条件、死锁和原子性违规,这进一步取决于一个或其他特定属性。

Testing Strategies

测试策略也被称为测试方法。此策略定义如何进行测试。测试方法具有两种技术 -

Proactive

一种方法是,在创建构建之前,尽早启动测试设计过程,以便找到并修复缺陷。

Reactive

另一种方法是,在开发过程完成之前不开始测试。

在对 python 程序应用任何测试策略或方法之前,我们必须对软件程序可能出现的错误类型有一个基本了解。错误如下 -

Syntactical errors

在程序开发过程中,可能有许多小错误。这些错误大多是因输入错误造成的。例如,缺少冒号或关键字拼写错误等。此类错误是由于程序语法错误,而不是逻辑错误造成的。因此,这些错误称为语法错误。

Semantic errors

语义错误也称为逻辑错误。如果软件程序中存在逻辑或语义错误,那么语句将编译并正确运行,但它不会给处所需的结果,因为逻辑不正确。

Unit Testing

这是最常用的用于测试 python 程序的测试策略之一。此策略用于测试代码的单元或组件。就单元或组件而言,是指代码的类或函数。单元测试通过测试“小”单元,简化了大型编程系统测试。在上述概念的帮助下,可以将单元测试定义为一种方法,其中测试源代码的各个单元以确定它们是否返回所需结果。

在我们后续的章节中,我们将了解有关单元测试的不同 Python 模块。

unittest module

用于单元测试的第一个模块是 unittest 模块。它受 JUnit 启发,并默认包含在 Python3.6 中。它支持测试自动化、用于测试的设置和关闭代码的共享、测试聚合到集合以及独立于报告框架的测试。

以下是 unittest 模块支持的一些重要概念

Text fixture

用于设置测试,以便它可以在测试开始前运行,并可以在测试完成之后中断。它可能涉及在测试开始之前创建临时数据库、目录等所需内容。

Test case

测试案例检查所需响应是否来自特定输入集。unittest 模块包含一个名为 TestCase 的基类,可用于创建新的测试案例。它包括两个默认方法 −

  1. setUp() − 用以在对测试夹具执行测试之前对其进行设置的挂钩方法。它在调用实现的测试方法之前被调用。

  2. tearDown( − 用以在运行类中的所有测试之后解构类夹具的挂钩方法。

Test suite

它是测试套件、测试案例或两者集合。

Test runner

它控制测试案例或测试套件的运行,并向用户提供结果。它可以使用 GUI 或简单文本界面来提供结果。

Example

以下 Python 程序使用 unittest 模块来测试名为 Fibonacci 的模块。该程序用于计算数字的斐波那契数列。在这个示例中,我们创建了一个名为 Fibo_test 的类,以使用不同的方法定义测试案例。这些方法是从 unittest.TestCase 继承的。我们使用两个默认方法 – setUp() 和 tearDown()。我们还定义 testfibocal 方法。测试的名称必须以字母 test 开始。在最终代码块中,unittest.main() 向测试脚本提供了一个命令行界面。

import unittest
def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a
class Fibo_Test(unittest.TestCase):
   def setUp(self):
   print("This is run before our tests would be executed")
   def tearDown(self):
   print("This is run after the completion of execution of our tests")

   def testfibocal(self):
   self.assertEqual(fib(0), 0)
   self.assertEqual(fib(1), 1)
   self.assertEqual(fib(5), 5)
   self.assertEqual(fib(10), 55)
   self.assertEqual(fib(20), 6765)

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

当从命令行运行时,以上脚本会生成如下所示的输出 −

Output

This runs before our tests would be executed.
This runs after the completion of execution of our tests.
.
----------------------------------------------------------------------
Ran 1 test in 0.006s
OK

现在,为了使它更清楚,我们更改了用于定义斐波那契模块的代码。

请将以下代码块作为示例 −

def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a

对代码块进行了一些更改,如下所示 −

def fibonacci(n):
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a

现在,在使用更改后的代码运行脚本后,将获得以下输出 −

This runs before our tests would be executed.
This runs after the completion of execution of our tests.
F
======================================================================
FAIL: testCalculation (__main__.Fibo_Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unitg.py", line 15, in testCalculation
self.assertEqual(fib(0), 0)
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 1 test in 0.007s

FAILED (failures = 1)

以上输出表明该模块未能给出会期输出。

Docktest module

docktest 模块也有助于单元测试。它还随 python 一起预先打包。它比 unittest 模块更易于使用。unittest 模块更适合于复杂测试。要使用 doctest 模块,我们需要导入它。相应函数的文档字符串必须带有交互式 python 会话及其输出。

如果我们的代码一切正常,那么 docktest 模块将不会有任何输出;否则,它将提供输出。

Example

以下 Python 示例使用 docktest 模块来测试名为 Fibonacci 的模块,该模块用于计算数字的斐波那契数列。

import doctest
def fibonacci(n):
   """
   Calculates the Fibonacci number

   >>> fibonacci(0)
   0
   >>> fibonacci(1)
   1
   >>> fibonacci(10)
   55
   >>> fibonacci(20)
   6765
   >>>

   """
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a
      if __name__ == "__main__":
   doctest.testmod()

我们可以看到,名为 fib 的相应函数的文档字符串带有交互式 python 会话及其输出。如果我们的代码正常,那么 docktest 模块将不会有任何输出。但是,为了查看它的工作原理,我们可以使用 –v 选项运行它。

(base) D:\ProgramData>python dock_test.py -v
Trying:
   fibonacci(0)
Expecting:
   0
ok
Trying:
   fibonacci(1)
Expecting:
   1
ok
Trying:
   fibonacci(10)
Expecting:
   55
ok
Trying:
   fibonacci(20)
Expecting:
   6765
ok
1 items had no tests:
   __main__
1 items passed all tests:
4 tests in __main__.fibonacci
4 tests in 2 items.
4 passed and 0 failed.
Test passed.

现在,我们将更改用于定义斐波那契模块的代码

请将以下代码块作为示例 −

def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a

以下代码块有助于做出更改 −

def fibonacci(n):
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a

运行脚本,即使在没有 -v 选项的情况下,也会获得如下所示的输出。

Output

(base) D:\ProgramData>python dock_test.py
**********************************************************************
File "unitg.py", line 6, in __main__.fibonacci
Failed example:
   fibonacci(0)
Expected:
   0
Got:
   1
**********************************************************************
File "unitg.py", line 10, in __main__.fibonacci
Failed example:
   fibonacci(10)
Expected:
   55
Got:
   89
**********************************************************************
File "unitg.py", line 12, in __main__.fibonacci
Failed example:
   fibonacci(20)
Expected:
   6765
Got:
   10946
**********************************************************************
1 items had failures:
   3 of 4 in __main__.fibonacci
***Test Failed*** 3 failures.

从以上输出中可以看到三个测试都失败了。