📝 面试求职: 「面试试题小程序」 ,内容涵盖 测试基础、Linux操作系统、MySQL数据库、Web功能测试、接口测试、APPium移动端测试、Python知识、Selenium自动化测试相关、性能测试、性能测试、计算机网络知识、Jmeter、HR面试,命中率杠杠的。(大家刷起来…)
📝 职场经验干货:
以下为作者观点:
如何测试一台机器?很简单,只需打开它,检查它是否工作。但如果它不工作,我们怎么知道是哪个部件出了问题呢?我们可能需要测试机器的每一个部件才能找出问题所在。在软件行业,这被称为单元测试。
单元测试是一种软件测试方法,其中应用程序的各个组件或模块(单元)被单独测试,以确保它们按预期工作。
对于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_yearclass 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 18, in test_senior"/Users/yang/PycharmProjects/PythonProject3/test_resume_scan.py", line 18, in test_seniorself.assertEqual(categorize_by_working_year(70), "Senior Engineer")AssertionError: 'Invalid working year: 70' != 'Senior Engineer'• Invalid working year: 70• Senior EngineerRan 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_yearclass TestCategorizeByWorkingYear(unittest.TestCase):@unittest.skip("暂时跳过这个")def test_graduate(self):self.assertEqual(categorize_by_working_year(1), "Graduate Engineer")@unittest.skipIf(sys.version_info < (3, 6), "仅适用于Python >= 3.6")def test_junior(self):self.assertEqual(categorize_by_working_year(3), "Junior Engineer")@unittest.skipUnless(sys.version_info > (3, 11), "仅适用于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.000sOK (skipped=1)
它显示有一个用例被跳过,unittest在其输出中友好地提醒了我们这一点。
3. 利用子测试和迭代更高效地编写测试
到目前为止,一切看起来都还不错,但我们编写测试的方式似乎不够Python化。
直观地说,我们可以编写一个迭代来循环测试一系列输入:
import unittestfrom resume_scan import categorize_by_working_yearclass TestCategorizeByWorkingYear(unittest.TestCase):def test_senior(self):for i in range(6, 12):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 EngineerRan 1 test in 0.000sFAILED (failures=1)
应该有两个失败的用例。一个是categorize_by_working_year(6),它返回“Experienced Engineer”,另一个是categorize_by_working_year(11),它返回“Principal Engineer”。但结果只显示了第一个,而且没有说明是哪个具体的测试用例导致了这个失败。
因为默认情况下,unittest在第一次失败时会停止测试循环,因此不会为每个案例单独报告失败消息。
如果我们想测试循环中的所有案例而不停止呢?
subTest上下文管理器就是适合这种情况的选择:
import unittestfrom resume_scan import categorize_by_working_yearclass TestCategorizeByWorkingYear(unittest.TestCase):def test_senior(self):for i in range(6, 10):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 EngineerRan 1 test in 0.000sFAILED (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_yearclass 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_yearclass 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 TestCategorizeByWorkingYeardef get_suite():entry_levels_suite = unittest.TestSuite()#添加单个测试entry_levels_suite.addTest(TestCategorizeByWorkingYear('test_graduate'))entry_levels_suite.addTest(TestCategorizeByWorkingYear('test_junior'))return entry_levels_suiteif 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_idclass 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_idclass 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 Testson: [push, pull_request]jobs:test:runs-on: ubuntu-lateststeps:- 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%免费】

626

被折叠的 条评论
为什么被折叠?



