从基本用法到高级技巧,Python中unittest的8个使用级别

📝 面试求职: 「面试试题小程序」 ,内容涵盖 测试基础、Linux操作系统、MySQL数据库、Web功能测试、接口测试、APPium移动端测试、Python知识、Selenium自动化测试相关、性能测试、性能测试、计算机网络知识、Jmeter、HR面试,命中率杠杠的。(大家刷起来…)

📝 职场经验干货:

软件测试工程师简历上如何编写个人信息(一周8个面试)

软件测试工程师简历上如何编写专业技能(一周8个面试)

软件测试工程师简历上如何编写项目经验(一周8个面试)

软件测试工程师简历上如何编写个人荣誉(一周8个面试)

软件测试行情分享(这些都不了解就别贸然冲了.)

软件测试面试重点,搞清楚这些轻松拿到年薪30W+

软件测试面试刷题小程序免费使用(永久使用)


以下为作者观点:

如何测试一台机器?很简单,只需打开它,检查它是否工作。但如果它不工作,我们怎么知道是哪个部件出了问题呢?我们可能需要测试机器的每一个部件才能找出问题所在。在软件行业,这被称为单元测试。

单元测试是一种软件测试方法,其中应用程序的各个组件或模块(单元)被单独测试,以确保它们按预期工作。

对于Python来说,有一个内置模块——unittest,它为大多数Python开发者可能遇到的测试场景提供了功能。但由于编写测试与编写其他程序不同,在复杂的测试场景下,你可能会对unittest模块的使用感到困惑。

本文通过从基础到高级的8个渐进级别,解释了如何利用unittest模块的强大功能,使你的代码健壮且无bug。此外,最后还有一个 bonus 级别,帮助你为生产环境做好一切准备。

1. 带有TestCase类的unittest基本用法


从根本上说,无论你要测试什么,都只需要3个步骤:

  • 设置输入

  • 执行代码/程序/机器并获取输出

  • 将输出与预期结果进行比较
     

unittest模块的目的只是为了简化所有单元测试的编写、组织和管理。因为它封装了常用的类和方法,我们只需要学习如何使用它们,并编写特定测试用例的核心逻辑。


假设我们有一个简单的Python脚本,名为“resume_scan.py”,它包含以下代码:
 

    def categorize_by_working_year(years):if 0 <= years <= 1:return "Graduate Engineer"elif 1 < years <= 3:return "Junior Engineer"elif 3 < years <= 6:return "Experienced Engineer"elif 6 < years <= 10:return "Senior Engineer"elif 10 < years <= 50:return "Principal Engineer"else:return f"Invalid working year: {years}"

    为简单起见,我们只实现了一个基本函数,根据工程师的总工作年限来检测其级别。(在现实中,我们不应该仅仅根据一个工程师的工作年限来判断其能力。此代码为方便起见简化了实际情况。)


    为了测试这个程序,让我们创建另一个名为“test_resume_scan.py”的Python文件来编写单元测试用例,如下所示:

      import unittestfrom resume_scan import categorize_by_working_year
      class TestCategorizeByWorkingYear(unittest.TestCase):def test_graduate(self):self.assertEqual(categorize_by_working_year(1), "Graduate Engineer")
      def test_junior(self):self.assertEqual(categorize_by_working_year(3), "Junior Engineer")
      def test_experienced(self):self.assertEqual(categorize_by_working_year(5), "Experienced Engineer")
      def test_senior(self):self.assertEqual(categorize_by_working_year(70), "Senior Engineer")
      def test_principal(self):self.assertEqual(categorize_by_working_year(15), "Principal Engineer")
      def test_negative(self):self.assertEqual(categorize_by_working_year(-1), "Invalid working year: -1")
      def test_too_old(self):self.assertEqual(categorize_by_working_year(199), "Invalid working year: 199")

      测试代码定义了一个继承自unittest.TestCase类的测试类,然后为特定的输入和输出指定了一些测试用例。


      self.assertEqual()方法用于定义一个断言。它的第一个参数是获取的输出,第二个是预期结果。如果它们不匹配,则表示测试用例失败。


      现在,我们可以通过终端中的简单命令执行单元测试:
       

        python -m unittest test_resume_scan.py


        测试结果如下:

          .....F.======================================================================FAIL: test_senior (test_resume_scan.TestCategorizeByWorkingYear.test_senior)----------------------------------------------------------------------Traceback (most recent call last):File "/Users/yang/PycharmProjects/PythonProject3/test_resume_scan.py", line 18in test_senior"/Users/yang/PycharmProjects/PythonProject3/test_resume_scan.py", line 18in test_seniorself.assertEqual(categorize_by_working_year(70), "Senior Engineer")AssertionError: 'Invalid working year: 70' != 'Senior Engineer'	•	Invalid working year: 70	•	Senior Engineer
          
          Ran 7 tests in 0.000s

          如上所示,单元测试的输出信息非常丰富。

          • 第一行是一个非常简洁的摘要,用一个点表示成功的测试,用“F”表示失败的测试。

          • 第二行告诉我们测试失败了,以及哪个测试失败了。

          • 然后它会显示失败测试的详细信息,这对调试很有用。

          • 最后一行告诉总共运行了多少个测试以及花费了多长时间。
             

          多亏了打印出的清晰信息,我们知道失败的原因是测试用例的定义不够严谨。
          让我们把它改成self.assertEqual(categorize_by_working_year(7), "Senior Engineer"),然后再次执行单元测试:

            .......----------------------------------------------------------------------Ran 7 tests in 0.000s


            OK完美。


            借助unittest模块,单元测试并不复杂。


            顺便说一下,注意我们将名称定义为“test_resume_scan.py”。这是一个约定,因为unittest会根据文件名来检测测试文件。如果一个文件的名称以“test”开头,unittest模块会将其视为测试代码文件。

            2. 必要时跳过某些测试


            有时,特别是当我们调试一个特定的bug并且有很多测试用例时,我们可能不想浪费时间再次运行一些不必要的测试用例。


            像unittest这样好的工具已经为我们考虑到了这种情况。它有3个内置的装饰器来跳过某些测试:

            • @unittest.skip(reason):跳过被装饰的测试

            • @unittest.skipIf(condition, reason):如果条件为True,则跳过被装饰的测试

            • @unittest.skipUnless(condition, reason):除非条件为True,否则跳过被装饰的测试
               

            例如,可以将前面的程序修改如下,以跳过一些测试用例:

              import sysimport unittestfrom resume_scan import categorize_by_working_year
              class TestCategorizeByWorkingYear(unittest.TestCase):@unittest.skip("暂时跳过这个")def test_graduate(self):self.assertEqual(categorize_by_working_year(1), "Graduate Engineer")
              @unittest.skipIf(sys.version_info < (36), "仅适用于Python >= 3.6")def test_junior(self):self.assertEqual(categorize_by_working_year(3), "Junior Engineer")
              @unittest.skipUnless(sys.version_info > (311), "仅适用于Python > 3.6")def test_experienced(self):self.assertEqual(categorize_by_working_year(5), "Experienced Engineer")
              def test_senior(self):self.assertEqual(categorize_by_working_year(7), "Senior Engineer")
              def test_principal(self):self.assertEqual(categorize_by_working_year(15), "Principal Engineer")
              def test_negative(self):self.assertEqual(categorize_by_working_year(-1), "Invalid working year: -1")
              def test_too_old(self):self.assertEqual(categorize_by_working_year(199), "Invalid working year: 199")

              现在,如果我们运行单元测试,结果是(在Python 3.12下执行):

                .s.....----------------------------------------------------------------------Ran 6 tests in 0.000s
                OK (skipped=1)

                它显示有一个用例被跳过,unittest在其输出中友好地提醒了我们这一点。

                3. 利用子测试和迭代更高效地编写测试


                到目前为止,一切看起来都还不错,但我们编写测试的方式似乎不够Python化。
                直观地说,我们可以编写一个迭代来循环测试一系列输入:

                 

                  import unittestfrom resume_scan import categorize_by_working_year
                  class TestCategorizeByWorkingYear(unittest.TestCase):def test_senior(self):for i in range(612):self.assertEqual(categorize_by_working_year(i), "Senior Engineer")

                  但测试结果似乎很奇怪:

                    F======================================================================FAIL: test_senior (test_resume_scan_2.TestCategorizeByWorkingYear.test_senior)----------------------------------------------------------------------Traceback (most recent call last):File "/Users/yang/PycharmProjects/PythonProject3/test_resume_scan_2.py", line 10, in test_seniorself.assertEqual(categorize_by_working_year(i), "Senior Engineer")AssertionError: 'Experienced Engineer' != 'Senior Engineer'- Experienced Engineer+ Senior Engineer
                    Ran 1 test in 0.000s
                    FAILED (failures=1)

                    应该有两个失败的用例。一个是categorize_by_working_year(6),它返回“Experienced Engineer”,另一个是categorize_by_working_year(11),它返回“Principal Engineer”。但结果只显示了第一个,而且没有说明是哪个具体的测试用例导致了这个失败。
                     

                    因为默认情况下,unittest在第一次失败时会停止测试循环,因此不会为每个案例单独报告失败消息。


                    如果我们想测试循环中的所有案例而不停止呢?


                    subTest上下文管理器就是适合这种情况的选择:
                     

                      import unittest
                      from resume_scan import categorize_by_working_year
                      class TestCategorizeByWorkingYear(unittest.TestCase):def test_senior(self):for i in range(610):with self.subTest(i=i):self.assertEqual(categorize_by_working_year(i), "Senior Engineer")

                      简单地说,subTest告诉测试运行器:“我正在这个方法里面运行小型测试。如果一个失败了,继续运行其他的——并分别报告每个失败。”
                       

                      添加这个上下文管理器后,结果再次变得全面且信息丰富:

                        FF======================================================================FAIL: test_senior (test_resume_scan_2.TestCategorizeByWorkingYear.test_senior) (i=6)----------------------------------------------------------------------Traceback (most recent call last):File "/Users/yang/PycharmProjects/PythonProject3/test_resume_scan_2.py", line 9, in test_seniorself.assertEqual(categorize_by_working_year(i), "Senior Engineer")AssertionError: 'Experienced Engineer' != 'Senior Engineer'	•	Experienced Engineer	•	Senior Engineer
                        ======================================================================FAIL: test_senior (test_resume_scan_2.TestCategorizeByWorkingYear.test_senior) (i=11)----------------------------------------------------------------------Traceback (most recent call last):File "/Users/yang/PycharmProjects/PythonProject3/test_resume_scan_2.py", line 9, in test_seniorself.assertEqual(categorize_by_working_year(i), "Senior Engineer")AssertionError: 'Principal Engineer' != 'Senior Engineer'	•	Principal Engineer	•	Senior Engineer
                        Ran 1 test in 0.000s
                        FAILED (failures=2)

                        编写带有subText的循环是一个很好的做法,这可以使我们的测试用例更简洁,更易于维护。

                        4. 熟练运用unittest的断言

                         

                        在之前的例子中,我们一直在使用assertEqual,但unittest提供了更多的断言选项,可以满足不同的测试需求。


                        正确使用常见的内置断言


                        就像其他Python模块一样优雅,unittest提供的大多数断言通过它们的名称就非常直观易懂,而且大多数现代代码编辑器会为它们提供自动补全功能。例如,当我在PyCharm中输入“assert”时,很多选项会显示出来:
                         

                        Pycharm中断言自动完成功能的屏幕截图


                        如上面的截图所示,我们可以很容易地根据断言的名称知道它的用法。


                        但是,有一种特殊的断言我们需要了解:用于测试异常的断言。
                         

                        使用断言测试异常


                        如果我们的代码在其行为中包含引发错误,那么了解这些预期的异常是否正常工作,对于全面测试一个代码单元来说意义重大。


                        幸运的是,unittest包含两个用于此目的的断言:

                        • .assertRaises(exc, fun, *args, **kwds):检查异常的类型是否正确,不包括其消息。

                        • .assertRaisesRegex(exc, r, fun, *args, **kwds):检查异常类型以及错误消息是否与模式匹配(如re.search所做的那样)
                           

                        让我们在之前的示例代码中添加异常处理逻辑,如下所示:

                          def categorize_by_working_year(years):if not isinstance(years, int):raise ValueError(f"Expected a number, got {type(years).name}")if years < 0 or years > 50:raise ValueError(f"Invalid working year: {years}")
                          if 0 <= years <= 1:return "Graduate Engineer"elif 1 < years <= 3:return "Junior Engineer"elif 3 < years <= 6:return "Experienced Engineer"elif 6 < years <= 10:return "Senior Engineer"else:return "Principal Engineer"

                          这个增强的代码会检查接收到的参数years,如果不符合要求,会引发带有相应消息的错误。


                          基于此,我们还需要修改单元测试代码来测试异常:
                           

                            import unittestfrom resume_scan import categorize_by_working_year
                            class TestCategorizeByWorkingYear(unittest.TestCase):
                            def test_invalid_negative_year(self):with self.assertRaises(ValueError):categorize_by_working_year(-1)
                            def test_invalid_too_large(self):with self.assertRaisesRegex(ValueError, "Invalid working year: 100"):categorize_by_working_year(100)
                            def test_invalid_type(self):with self.assertRaisesRegex(ValueError, "Expected a number, got str"):categorize_by_working_year("five")
                            def test_valid_category(self):self.assertEqual(categorize_by_working_year(2), "Junior Engineer")self.assertEqual(categorize_by_working_year(1), "Graduate Engineer")self.assertEqual(categorize_by_working_year(4), "Experienced Engineer")self.assertEqual(categorize_by_working_year(7), "Senior Engineer")self.assertEqual(categorize_by_working_year(20), "Principal Engineer")

                            我们添加了self.assertRaises(ValueError)来测试当输入为-1时是否能正确地引发错误类型。还添加了另外两个测试用例——self.assertRaisesRegex(ValueError, "Invalid working year: 100")和self.assertRaisesRegex(ValueError, "Expected a number, got str"),以测试异常是否能以预期的错误类型和消息正确引发。
                             

                            现在,让我们执行它并得到测试结果:
                             

                              ....----------------------------------------------------------------------Ran 4 tests in 0.000s


                              OK,所有内容都测试通过了,包括异常!
                               

                              必要时定义自定义断言方法


                              有时,编写自定义断言是一个更好的选择。


                              一方面,当程序变得更复杂时,并非所有情况都能通过内置断言轻松覆盖。另一方面,内置断言非常基础,它们的默认消息对于特定情况来说信息量不够。
                               

                              例如,我们可以为“resume_scan.py”脚本定义一个名为assertCatrgoryEqual的断言:
                               

                                import unittestfrom resume_scan import categorize_by_working_year
                                class TestCategorizeByWorkingYear(unittest.TestCase):def assertCategoryEqual(self, years, expected_category):"""用于比较工作年限类别并带有友好错误消息的自定义断言。"""actual = categorize_by_working_year(years)self.assertEqual(actual, expected_category,msg=f"对于{years}年,预期类别为'{expected_category}',但得到的是'{actual}'。")
                                def test_all_valid_categories(self):self.assertCategoryEqual(9"Graduate Engineer")self.assertCategoryEqual(1"Graduate Engineer")self.assertCategoryEqual(2"Junior Engineer")self.assertCategoryEqual(5"Experienced Engineer")self.assertCategoryEqual(7"Senior Engineer")self.assertCategoryEqual(20"Principal Engineer")

                                多亏了这个自定义断言,我们的单元测试代码更简洁、更好:
                                无需在后续的每个断言中再次输入长长的函数名(否则我们仍然需要像这样输入self.assertEqual(categorize_by_working_year(3), "Junior Engineer"))
                                由于自定义消息,更容易调试失败的案例。

                                5. 使用unittest的高级命令行技巧

                                 

                                我们知道,可以通过终端中的以下命令执行单元测试文件:
                                 

                                  python -m unittest test_resume_scan.py

                                  然而,对于一个更大的项目,可能会有很多测试文件。我们需要一个一个地输入它们吗?


                                  当然不需要。


                                  实际上,unittest模块附带了一个非常强大的CLI(命令行界面),我们可能忽略了它。列举几个:


                                  发现并运行目录中的所有测试


                                  还记得第1级中提到的单元测试文件的命名约定吗?是的,只要一个文件的名称以“test”开头,它就会被unittest自动发现。
                                   

                                  因此,如果有很多测试文件要运行,我们只需要在执行命令的末尾添加“discover”关键字,然后unittest就会运行所有这些文件:
                                   

                                    python -m unittest discover

                                    我们也可以进行一些自定义:

                                      python -m unittest discover -s tests -p "t_.py"

                                      上面的命令添加了两个参数:

                                      • -s:定义搜索单元测试文件的目录

                                      • -p:更改文件名模式(默认是 test.py)

                                      顺便说一下,如果我们只运行 python -m unittest 而不带任何参数,它与运行 python -m unittest discover 是一样的。但一个好的做法是添加“discover”关键字,以确保命令清晰,并且我们知道会发生什么。

                                      详细输出 

                                      如果需要打印更多信息,我们可以添加参数 -v:

                                        python -m unittest -v

                                        它表示详细输出。它会在测试结果中更清晰地显示每个测试用例的名称和结果。

                                        遇到第一个失败时停止 

                                        在调试大型项目时,每次运行所有单元测试都是非常耗时的操作。为了节省时间和精力,我们可以添加 -f 参数,让测试运行器在任何测试失败时立即退出:

                                          python -m unittest -f

                                           6. 使用 TestSuite 类对单元测试进行分组

                                          当项目变得越来越大,其相应的单元测试也越来越多时,这一直是一个挑战。因此,将来自不同测试文件的几个单元测试组合成一个套件并一起测试,是一种非常有效的调试方法。

                                          unittest 模块为此提供了 TestSuite 类:

                                            import unittestfrom test_resume_scan import TestCategorizeByWorkingYear
                                            def get_suite():entry_levels_suite = unittest.TestSuite()
                                            #添加单个测试entry_levels_suite.addTest(TestCategorizeByWorkingYear('test_graduate'))entry_levels_suite.addTest(TestCategorizeByWorkingYear('test_junior'))
                                            return entry_levels_suite
                                            if name == "main":runner = unittest.TextTestRunner(verbosity=2)runner.run(get_suite())

                                            上面的代码将两个测试分组到一个套件中(这两个测试基于第 1 级的“test_resume_scan.py”文件),其执行结果如下:

                                              test_graduate (test_resume_scan.TestCategorizeByWorkingYear.test_graduate) ... oktest_junior (test_resume_scan.TestCategorizeByWorkingYear.test_junior) ... okRan 2 tests in 0.000s

                                              清晰且信息丰富,不是吗?

                                              但是,如果有一个大型测试类,并且需要其中的所有测试,我们是否必须一个一个地添加每个测试用例呢?

                                              完全不必。unittest.TestLoader 是一个实用工具,它可以帮助我们从类或模块中自动发现并加载测试用例,而无需手动按名称添加每个测试:

                                                suite = unittest.TestSuite()loader = unittest.TestLoader()suite.addTests(loader.loadTestsFromTestCase(TestCategorizeByWorkingYear))

                                                 7. 利用 unittest.mock 用假数据或操作模拟真实数据或操作

                                                真实的应用程序可能比我们的示例复杂得多,相应的单元测试可能难以编写。

                                                例如,如果我们的“resume_scan.py”程序需要从外部资源(如数据库、API、其他文件等)获取数据,如何编写单元测试呢?

                                                当然,我们可以真的从其原始来源检索数据并进行测试。但这个想法有一些缺点:

                                                • 单元测试执行缓慢:获取外部数据会引入 I/O 延迟。随着时间的推移,随着我们的测试套件不断扩大,它会变得非常缓慢。

                                                • 不稳定的测试:依赖外部数据的测试可能会由于外部原因(如网络问题、超时或数据不一致)而随机失败。这使得我们的单元测试结果不可靠。

                                                • 不必要的计算资源成本:如果外部 API 调用不是免费的怎么办?那么单元测试将是一项昂贵的操作。

                                                单元测试应该只关注我们正在测试的逻辑,而不是其依赖项。

                                                幸运的是,我们可以使用 unittest.mock 模块来避免在单元测试期间进行真实的数据检索。

                                                模拟意味着在测试期间用假对象或行为(如数据库调用、API 请求、文件 I/O)替换真实的对象或行为,这样我们就可以专注于隔离测试代码。

                                                这个模块有很多功能。不必一下子全部了解。让我们尝试一个例子,看看它有多有用:

                                                  def get_user_years_from_api(user_id):"""模拟 API 调用。在实际代码中,这将使用 requests 或 httpx。"""
                                                  def categorize_user_by_id(user_id):years = get_user_years_from_api(user_id)
                                                  if not isinstance(years, int):raise ValueError(f"Expected a number, got {type(years).name}")if years < 0 or years > 50:raise ValueError(f"Invalid working year: {years}")
                                                  if 0 <= years <= 1:return "Graduate Engineer"elif 1 < years <= 3:return "Junior Engineer"elif 3 < years <= 6:return "Experienced Engineer"elif 6 < years <= 10:return "Senior Engineer"else:return "Principal Engineer"

                                                  现在,“resume_scan.py”脚本从 API 获取“years”。我们并没有真正实现 API 调用函数,但借助模拟方法,我们仍然可以进行单元测试:

                                                    import unittestfrom unittest.mock import patchfrom resume_scan import categorize_user_by_id
                                                    class TestCategorizeWithAPI(unittest.TestCase):
                                                    @patch('resume_scan.get_user_years_from_api')def test_experienced_engineer(self, mock_api):mock_api.return_value = 5result = categorize_user_by_id(user_id=101)self.assertEqual(result, "Experienced Engineer")mock_api.assert_called_once_with(101)
                                                    @patch('resume_scan.get_user_years_from_api')def test_invalid_type(self, mock_api):mock_api.return_value = "ten"with self.assertRaises(ValueError) as ctx:categorize_user_by_id(42)self.assertIn("Expected a number"str(ctx.exception))
                                                    @patch('resume_scan.get_user_years_from_api')def test_out_of_range(self, mock_api):mock_api.return_value = 100with self.assertRaises(ValueError):categorize_user_by_id(999)
                                                    @patch('resume_scan.get_user_years_from_api')def test_api_failure(self, mock_api):mock_api.side_effect = ConnectionError("API timeout")with self.assertRaises(ConnectionError):categorize_user_by_id(1)

                                                    上述单元测试代码的重点:

                                                    • @patch('categorize.get_user_years_from_api'):这个装饰器在测试期间替换 API 调用。

                                                    • mock_api.return_value = 5:模拟 API 返回整数“5”

                                                    • mock_api.side_effect = ConnectionError(...):模拟 API 调用引发的异常。

                                                    • mock_api.assert_called_once_with(101):验证 API 被正确调用。

                                                     8. 使用装置(Fixtures)为单元测试定义一致的上下文

                                                    前面的测试代码在每个测试方法中重复编写相同的装饰器 @patch('categorize.get_user_years_from_api')。这不够 Python 风格,并且违反了 DRY(不要重复自己)原则。

                                                    为了改进它,我们需要了解装置(fixtures)的概念。

                                                    在 Python 的 unittest 框架中,装置指的是在每个测试方法之前和之后运行,或者为整个测试类/模块运行一次的设置和清理代码。

                                                    简单地说,它帮助我们管理共享的测试数据或模拟对象,作为特定范围的单元测试的一致上下文。

                                                    让我们将装置的思想应用到我们的单元测试代码中:

                                                      import unittestfrom unittest.mock import patchfrom resume_scan import categorize_user_by_id
                                                      class TestCategorizeWithFixture(unittest.TestCase):def setUp(self):# 在每个测试之前开始修补外部 APIpatcher = patch('resume_scan.get_user_years_from_api')self.mock_api = patcher.start()self.addCleanup(patcher.stop)  # 确保在测试后停止
                                                      def test_junior_engineer(self):self.mock_api.return_value = 2result = categorize_user_by_id(123)self.assertEqual(result, "Junior Engineer")
                                                      def test_invalid_range(self):self.mock_api.return_value = 100with self.assertRaises(ValueError):categorize_user_by_id(456)
                                                      def test_invalid_type(self):self.mock_api.return_value = "ten"with self.assertRaises(ValueError):categorize_user_by_id(789)

                                                      如上面的程序所示,我们可以使用 setUp() 方法将假的外部 API 设置为所有后续测试的上下文,而不是在类中的每个测试上添加“patch”装饰器。这段代码是不是更易于维护和更可靠呢?

                                                      附加级别:使用 CI/CD 工具设置自动单元测试

                                                      现在我们已经了解了 Python 中 unittest 模块的大部分常见用法。但在生产环境中,什么时候进行单元测试最好呢?

                                                      正确的做法是,每次开发人员提交或推送代码时,测试都应该自动运行,如果有任何问题,立即给出反馈。

                                                      但如果有些开发人员忘记这样做了呢?这就是为什么在专业的软件开发中,使用 CI/CD 工具进行自动单元测试是一个必不可少的过程。

                                                      CI(持续集成)是每次将更改推送到存储库时自动构建和测试代码的实践。

                                                      CD(持续交付或持续部署)是将经过测试的代码自动交付到 staging 环境或生产环境的过程。

                                                      总之,CI/CD 是测试和部署的自动化。

                                                      设置 CI/CD 工具非常直观,有很多流行的选择,例如:

                                                      • GitHub Actions

                                                      • GitLab CI/CD

                                                      • CircleCI

                                                      • Travis CI

                                                      • Jenkins

                                                      • Bitbucket Pipelines

                                                      每个工具可能有不同的配置自动测试的方式,让我们编写一个 GitHub Actions 工作流(YAML 格式文件)来快速了解一下:

                                                        name: Run Unit Tests
                                                        on: [push, pull_request]
                                                        jobs:test:runs-on: ubuntu-latest
                                                        steps:- uses: actions/checkout@v3
                                                        - name: Set up Pythonuses: actions/setup-python@v4with:python-version: '3.12'
                                                        - name: Install dependenciesrun: |python -m pip install --upgrade pippip install -r requirements.txt
                                                        - name: Run testsrun: python -m unittest discover

                                                        每次开发人员将代码推送到 GitHub 时,这个工作流将:

                                                        • 检出代码

                                                        • 设置 Python

                                                        • 安装依赖项

                                                        • 运行所有单元测试

                                                        因此,将单元测试添加到 CI/CD 工具中,例如上面的 GitHub Actions 工作流,可以确保只有可运行的、经过测试的代码才能继续推进。

                                                        结论

                                                        单元测试是每个 Python 开发人员都应该掌握的最重要技能之一。借助 Python 内置的 unittest 框架,编写可靠、隔离且可重复的测试不再困难。

                                                        我们探讨了如何:

                                                        • 编写基本的单元测试

                                                        • 跳过某些测试用例

                                                        • 编写测试循环

                                                        • 应用不同的内置断言甚至自定义断言

                                                        • 使用 CLI 参数控制 unittest 的行为

                                                        • 将单元测试分组为套件

                                                        • 使用装置管理测试上下文

                                                        • 使用 unittest.mock 模块模拟外部依赖项

                                                        • 通过 CI/CD 管道进行自动单元测试

                                                        🎯 无论你是独自工作还是在一个大型团队中,构建一个坚实的测试套件都是一项强大的能力。从小处着手,尽早编写测试,并让单元测试成为你开发常规的一部分。

                                                        最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

                                                        ​​​

                                                        评论
                                                        添加红包

                                                        请填写红包祝福语或标题

                                                        红包个数最小为10个

                                                        红包金额最低5元

                                                        当前余额3.43前往充值 >
                                                        需支付:10.00
                                                        成就一亿技术人!
                                                        领取后你会自动成为博主和红包主的粉丝 规则
                                                        hope_wisdom
                                                        发出的红包
                                                        实付
                                                        使用余额支付
                                                        点击重新获取
                                                        扫码支付
                                                        钱包余额 0

                                                        抵扣说明:

                                                        1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                                                        2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                                                        余额充值