Concurrency In Python 简明教程

Testing Thread Applications

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

In this chapter, we will learn about testing of thread applications. We will also learn the importance of testing.

Why to Test?

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

Before we dive into the discussion about the importance of testing, we need to know what is testing. In general terms, testing is a technique of finding out how well something is working. On the other hand, specifically if we talk about computer programs or software then testing is the technique of accessing the functionality of a software program.

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

In this section, we will discuss the importance of software testing. In software development, there must be double-checking before the releasing of software to the client. That is why it is very important to test the software by experienced testing team. Consider the following points to understand the importance of software testing −

Improvement of software quality

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

Certainly, no company wants to deliver low quality software and no client wants to buy low quality software. Testing improves the quality of software by finding and fixing the bugs in that.

Satisfaction of customers

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

The most important part of any business is the satisfaction of their customers. By providing bug free and good quality software, the companies can achieve customer satisfaction.

Lessen the impact of new features

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

Suppose we have made a software system of 10000 lines and we need to add a new feature then the development team would have the concern about the impact of this new feature on whole software. Here, also, testing plays a vital role because if the testing team has made a good suite of tests then it can save us from any potential catastrophic breaks.

User experience

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

Another most important part of any business is the experience of the users of that product. Only testing can assure that the end user finds it simple and easy to use the product.

Cutting down the expenses

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

Testing can cut down the total cost of software by finding and fixing the bugs in testing phase of its development rather than fixing it after delivery. If there is a major bug after the delivery of the software then it would increase its tangible cost say in terms of expenses and intangible cost say in terms of customer dissatisfaction, company’s negative reputation etc.

What to Test?

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

It is always recommended to have appropriate knowledge of what is to be tested. In this section, we will first understand be the prime motive of tester while testing any software. Code coverage, i.e., how many lines of code our test suite hits, while testing, should be avoided. It is because, while testing, focusing only on the number of lines of codes adds no real value to our system. There may remain some bugs, which reflect later at a later stage even after deployment.

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

Consider the following important points related to what to test −

  1. We need to focus on testing the functionality of the code rather than the code coverage.

  2. We need to test the most important parts of the code first and then move towards the less important parts of the code. It will definitely save time.

  3. The tester must have multitude different tests that can push the software up to its limits.

Approaches for testing concurrent software programs

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

Due to the capability of utilizing the true capability of multi-core architecture, concurrent software systems are replacing sequential systems. In recent times, concurrent system programs are being used in everything from mobile phones to washing machines, from cars to airplanes, etc. We need to be more careful about testing the concurrent software programs because if we have added multiple threads to single thread application having already a bug, then we would end up with multiple bugs.

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

Testing techniques for concurrent software programs are extensively focusing on selecting interleaving that expose potentially harmful patterns like race conditions, deadlocks and violation of atomicity. Following are two approaches for testing concurrent software programs −

Systematic exploration

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

This approach aims to explore the space of the interleavings as broadly as possible. Such approaches can adopt a brute-force technique and others adopt partial order reduction technique or heuristic technique to explore the space of interleavings.

Property-driven

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

Property-driven approaches rely on the observation that concurrency faults are more likely to occur under interleavings that expose specific properties such as suspicious memory access pattern. Different property-driven approaches target different faults like race conditions, deadlocks and violation of atomicity, which further depends on one or other specific properties.

Testing Strategies

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

Test Strategy is also known as test approach. The strategy defines how testing would be carried out. Test approach has two techniques −

Proactive

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

An approach in which the test design process is initiated as early as possible in order to find and fix the defects before the build is created.

Reactive

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

An approach in which the testing does not start until the completion of the development process.

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

Before applying any test strategy or approach on python program, we must have a basic idea about the kind of errors a software program may have. The errors are as follows −

Syntactical errors

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

During program development, there can be many small errors. The errors are mostly due to typing mistakes. For example, missing colon or a wrong spelling of a keyword, etc. Such errors are due to the mistake in program syntax and not in logic. Hence, these errors are called syntactical errors.

Semantic errors

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

The semantic errors are also called logical errors. If there is a logical or semantic error in software program then the statement will compile and run correctly but it will not give the desired output because the logic is not correct.

Unit Testing

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

This is one of the most used testing strategies for testing python programs. This strategy is used for testing units or components of the code. By units or components, we mean classes or functions of the code. Unit testing simplifies the testing of large programming systems by testing “small” units. With the help of the above concept, unit testing may be defined as a method where individual units of source code are tested to determine if they return the desired output.

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

In our subsequent sections, we will learn about the different Python modules for unit testing.

unittest module

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

The very first module for unit testing is the unittest module. It is inspired by JUnit and by default included in Python3.6. It supports test automation, sharing of setup and shutdown code for tests, aggregation of tests into collections, and independence of the tests from the reporting framework.

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

Following are a few important concepts supported by the unittest module

Text fixture

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

It is used to set up a test so that it can be run before starting the test and tear down after the finish of test. It may involve creation of temporary database, directories, etc. needed before starting the test.

Test case

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

The test case checks whether a required response is coming from the specific set of inputs or not. The unittest module includes a base class named TestCase which can be used to create new test cases. It includes two by default methods −

  1. setUp() − a hook method for setting up the test fixture before exercising it. This is called before calling the implemented test methods.

  2. tearDown( − a hook method for deconstructing the class fixture after running all tests in the class.

Test suite

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

It is a collection of test suites, test cases or both.

Test runner

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

It controls the running of the test cases or suits and provides the outcome to the user. It may use GUI or simple text interface for providing the outcome.

Example

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

The following Python program uses the unittest module to test a module named Fibonacci. The program helps in calculating the Fibonacci series of a number. In this example, we have created a class named Fibo_test, to define the test cases by using different methods. These methods are inherited from unittest.TestCase. We are using two by default methods – setUp() and tearDown(). We also define the testfibocal method. The name of the test must be started with the letter test. In the final block, unittest.main() provides a command-line interface to the test script.

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()

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

When run from the command line, the above script produces an output that looks like this −

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

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

Now, to make it clearer, we are changing our code which helped in defining the Fibonacci module.

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

Consider the following code block as an example −

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

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

A few changes to the code block are made as shown below −

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

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

Now, after running the script with the changed code, we will get the following output −

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)

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

The above output shows that the module has failed to give the desired output.

Docktest module

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

The docktest module also helps in unit testing. It also comes prepackaged with python. It is easier to use than the unittest module. The unittest module is more suitable for complex tests. For using the doctest module, we need to import it. The docstring of the corresponding function must have interactive python session along with their outputs.

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

If everything is fine in our code then there will be no output from the docktest module; otherwise, it will provide the output.

Example

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

The following Python example uses the docktest module to test a module named Fibonacci , which helps in calculating the Fibonacci series of a number.

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 选项运行它。

We can see that the docstring of the corresponding function named fib had interactive python session along with the outputs. If our code is fine then there would be no output from the doctest module. But to see how it works we can run it with the –v option.

(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.

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

Now, we will change the code that helped in defining the Fibonacci module

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

Consider the following code block as an example −

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

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

The following code block helps with the changes −

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

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

After running the script even without the –v option, with the changed code, we will get the output as shown below.

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.

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

We can see in the above output that three tests have failed.