Python3 - 开始python编程(十六)

Python3 - 开始python编程(十六)

上一篇文章中,我们介绍了多处理,完成异步。在本文中,我们将介绍所有编程语言中最关键的方面: 测试驱动开发。


测试

用于测试代码的两个最受欢迎的模块是”unittest”和”PyTest”。我选择在本文中介绍”unittest”,因为它是 Python 附带的。

unittest 是 Python 提供的框架,可让您创建有关代码的断言。如果断言为真,则测试通过。

在进行测试之前,我们应该介绍一些有关测试代码的内容,其中一些内容被社区划分了很多。

有几种不同的测试形式:

  • 单元测试-覆盖代码各个部分的测试(本文所涉及的内容)
  • 可靠性测试(冒烟测试)-确保您的完整程序能够经受时间考验或不会崩溃的测试。 (名称 smoke test 源自一个古老的说法,即如果您打开某些东西并且它没有开始冒烟,那么它应该可以工作。)
  • 质量保证测试-由质量检查小组进行的测试(如果有的话)。质量检查工程师受过训练,可以使您的代码混乱无比。 (对质量检查工程师:我很爱地说。)

  • 用户验收测试-您的代码将进入一个测试您的代码的试验小组。试点小组越大,您得到的结果就越多。 (例如,抢先体验游戏)

这些测试可以按任何顺序进行,但这是典型的使用顺序。

  • 代码覆盖率-测试覆盖的代码百分比。
  • 覆盖率-定义测试次数与代码库中行数的比率。

我知道这些都是棘手的主题,因此我将进行理性讨论。

拥有 100%的代码覆盖总是很不错的,但是至少您应该专注于应用程序的业务逻辑,这是程序创建时要执行的代码的主要部分。在大多数情况下,尽管可以通过一些精心设计的测试轻松获得 80%的覆盖率,但最低的覆盖率应该是 30%。精心设计的测试涵盖了代码的广泛工作流程,例如检查来自 Web 服务器应用程序的响应以确保其正确。您将获得路由,业务逻辑甚至数据库逻辑,它们都包含在一个测试中。

覆盖率通常是某些开发人员的自吹自 point。有人会说“我的比例为 1:1”(对一行代码进行一次测试)。这可能意味着浪费了大量的生产力周期。在大中型程序中,更好的目标是 1:10 或什至 1:20。这在浪费的生产率和测试准确性之间提供了中间立场。

  • 测试驱动开发(TDD)-TDD 是首先与code 对比,然后编写 tests的方法。 TDD 首先编写一个测试,然后编写最少的代码以使测试通过。

TDD 和覆盖率通常是齐头并进的;如果您听到开发人员谈论他们的测试比率,那么他们可能正在使用 TDD。 TDD 并不是一件坏事,但是如果您不小心的话,它会很快引起维护麻烦。

例如:

1
2
3
4
5
6
7
8
9
10
import unittest


def add(x, y):
    return x + y

class TestAddFunction(unittest.TestCase):
    def test_add_numbers(self):
        result = add(2, 2)
        self.assertEqual(result, 4, "Whoops")

首先,我们需要“导入 unittest”以引入用于测试的模块。接下来,我们有一个基本的 add 函数,将两个数字相加。

现在,我们创建一个名为 TestAddFunction 的类,该类继承自 unittest.TestCase,并将包含为上述 add 函数创建的每个测试。在实际情况下,您的课程将包含与工作流程功能相关的所有测试。如果工作流程正在从文件中检索名称,则可以测试打开文件,读取文件,解析名称并确保从中检索到的名称的值和数量。文件正确。

我们创建的第一个测试是”test_add_numbers”。它的唯一责任是测试我们的 add 函数是否返回了正确的结果。首先,我们调用函数并将结果存储在变量中。最后,我们使用 self.assertEqual(result,4,”Whoops”)`检查我们函数的结果确实为四个。由于我们继承自 unittest.TestCase,因此我们收到了许多断言来帮助我们测试代码。

“assertEqual”带有三个参数。前两个是我们要比较的值,第三个是如果测试失败,我们要打印到控制台的消息。在这个基本示例中,很明显测试失败的原因,但是有时您可能有多个断言,这有助于确定哪个断言失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import unittest


def add(x, y):
    return x + y

class TestAddFunction(unittest.TestCase):
    def test_add_numbers(self):
        result = add(2, 2)
        self.assertEqual(result, 4, "Bad Number")

    def test_add_strings(self):
        result = add("Hello,", " World")
        self.assertEqual(result, "Hello, World", "Bad String")

为了运行测试,您可以使用以下命令:

1
2
cd /path/to/project
python -m unittest

如果在项目目录中创建了虚拟环境,则需要在末尾指定文件名:

1
python -m unittest tests.py

在 PyCharm 中,您需要为测试添加运行配置

测试前置和后置工作

有时您需要在测试前后进行工作。 setUp 和 tearDown 允许您配置测试环境,因此您无需在生产系统中进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import unittest
import os


def read_file(name):
    with open(name, 'r') as file:
        return file.read()

class TestCreateDeleteFile(unittest.TestCase):
    def setUp(self) -> None:
        with open("testfile", "w") as file:
            file.write("Hello")

    def tearDown(self) -> None:
        os.remove("testfile")

    def test_file_read(self):
        contents = read_file('testfile')
        self.assertTrue(len(contents), "Empty file")
        self.assertEqual(contents, "Hello", "File content mismatch")

这只是确保文件不为空的简单测试。在这里,我们使用self.assertTrue(len(contents),"Empty file")进行测试。 assertTrue需要一个布尔表达式。空字符串,“无”和”0”也等于“假”结果。

我们的主要重点是”setUp”和”tearDown”。在创建我们的类时会调用”setUp”。对于测试类,可以将其视为def __init __(self):。在这里,我们使用它来创建一个新文件,其中包含单词”Hello”。随着测试的继续,它将调用 test_file_read,后者将调用函数 read_file 并将响应存储到 contents 中。然后,我们检查以确保“内容”不仅仅是一个空字符串;如果是的话,我们打印“空文件”。

我们的最后检查是断言文件的内容等于”Hello”。这看起来像是重复测试,但是通过首先检查内容的长度,我们可以确保文件至少存在。最终测试是确保文件内容符合我们的预期。

当我们所有的测试都针对该类运行时,将调用tearDown。这是我们执行所需清理的地方。在这种情况下,我们在项目目录中创建了一个名为testfile的文件,我们需要将其删除。


覆盖率

关于测试的内容还有很多,但是我将在下面的建议阅读中保留给您。我想谈一谈覆盖率,因为这是您完成测试后的了解方式。如果没有覆盖,则需要遵循格言“编写测试,直到由于编写测试而对失败的恐惧变得无聊为止。”

您可以使用`pip install coverage’或使用 Pycharm 中捆绑的 Coverage 来安装 Coverage。 (设置>构建,执行,部署> Coverage>使用捆绑的 coverage.py)

您可以使用以下命令从命令行运行和检查测试范围:

1
2
3
cd /path/to/project
python -m coverage run
python -m coverage report

这将输出类似于以下内容的输出:

1
2
3
Name      Stmts   Miss  Cover
-----------------------------
tests.py     13      0   100%

“名称”是包含测试的文件,”stmts”是在文件中找到的语句数,“小姐”是覆盖率错过的测试数,而“覆盖”是代码所占的百分比被测试覆盖。

要查看选项列表,请使用 python -m coverage help。

在 PyCharm 中,选择“运行”>“运行具有覆盖范围的测试”。测试遮盖了绿色阴影线。您还将在右侧栏中看到每个文件的统计信息(默认布局)。


测试驱动开发(TDD)

TDD 是一种测试代码的方法。这个想法是,您将编写一个失败的测试,然后只编写足够的代码以使测试通过。描述此问题的最直接方法是遵循以下步骤:

  1. 添加测试
  2. 运行测试。如果通过,请转到步骤 1。
  3. 进行更改。
  4. 运行测试。如果通过,请转到步骤 1。如果失败,请转到第三步。

在前面的示例中,我们编写了一个函数,然后编写了可以一次性调用该函数的测试。在 TDD 中,我们会编写测试并运行它,完全知道测试将由于逻辑不存在而失败。然后,我们只需编写足够的代码即可通过测试。如果仍然失败,请返回并检查逻辑以找出失败原因并进行更正。如果通过了,我们继续前进。

由于这只是一种方法,因此在这里我将不介绍它,但是如果您想了解更多信息,我将在建议阅读中提供一个链接。


摘要

我们学习了如何在 Python 中进行一些基本测试,以及如何使用代码覆盖率来弄清楚测试涵盖了我们多少代码,并简要介绍了 TDD。

我以前很难编写测试,因为我编写的大多数程序都很简单。我不得不改变心态。我开始认为,我写的每个程序每天都会赚到 100 万美元,直到失败为止。为了防止它失败,我需要编写测试以确保它无需维护即可运行数十年。


建议阅读

测试断言的完整列表

unittest —单元测试框架— Python 3.7.4 文档

覆盖率

Coverage.py — Coverage.py 4.5.3 文档

测试驱动开发

测试驱动开发(TDD)简介

PyTest

pytest:帮助您编写更好的程序— pytest 文档


下一步是什么?

在本系列的最后一篇文章中,我将提供一些用于开发工作流的技巧。虽然它对我有用,但对于初学者来说可能是一个很好的起点,但可能并不适合所有人。

熟能生巧,在 GitHub 上阅读其他开发人员的代码也是如此。我强烈建议您出去看看其他 Python 项目,并向其他项目学习。

Rating: