第22章 走进xUnit:测试驱动开发的关键工具(持续探索)
写在前面
这本书是我们老板推荐过的,我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后,我突然思考,对于测试开发工程师来说,什么才更有价值呢?如何让 AI 工具更好地辅助自己写代码,或许优质的单元测试是一个切入点。 就我个人而言,这本书确实很有帮助。第一次读的时候,很多细节我都不太懂,但将书中内容应用到工作中后,我受益匪浅。比如面对一些让人抓狂的代码设计时,书里的方法能让我逐步深入理解代码的逻辑与设计。 作为一名测试开发工程师,我想把学习这本书的经验分享给大家,希望能给大家带来帮助。因为现在工作中大多使用 Python 代码,所以我把书中JAVA案例都用 Python 代码进行了改写 。
在前面几篇博客中,我们围绕xUnit测试框架进行了多方面的探索,包括测试的准备、清理、计数等功能的实现。本篇博客将聚焦于如何处理未通过的测试用例,进一步完善我们对xUnit框架的理解与实践。
处理未通过用例的测试需求
在测试过程中,准确识别和处理未通过的测试用例至关重要。我们需要编写更精细的测试,以确保在有测试未通过的情况下,能够正确地记录和报告相关信息。具体来说,我们期望testStarted()和testFailed()等方法能够在测试开始和失败时,将对应消息准确发送到结果当中,以便正确输出测试结果。
代码实现步骤
编写测试方法验证结果格式化
首先,在TestCaseTest类中编写testFailedResultFormatting方法,用于验证测试失败结果的格式化是否正确:
class TestCaseTest(TestCase):
def testFailedResultFormatting(self):
result = TestResult()
result.testStarted()
result.testFailed()
assert "1 run, 1 failed" == result.summary()
更新TestResult类以记录失败次数
为了准确记录测试失败的次数,对TestResult类进行如下更新:
class TestResult:
def __init__(self):
self.runCount = 0
self.errorCount = 0
def testStarted(self):
self.runCount = self.runCount + 1
def testFailed(self):
self.errorCount = self.errorCount + 1
def summary(self):
return "%d run, %d failed" % (self.runCount, self.errorCount)
修改TestCase类的run方法以捕获异常
在TestCase类的run方法中,通过try - except语句捕获测试方法抛出的异常,并调用result.testFailed()来记录测试失败:
class TestCase:
def __init__(self, name):
self.name = name
def setUp(self):
pass
def tearDown(self):
pass
def run(self):
result = TestResult()
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
self.tearDown()
return result
潜在问题与待办事项
在上述代码实现中,存在一个潜在问题:如果在setUp()方法执行过程中出现严重问题,产生的异常将不会被捕获。这与我们期望测试彼此独立运行的初衷不符。我们将此问题记录在待办事项中,后续再进行处理。
完整示例代码
class TestResult:
def __init__(self):
self.runCount = 0
self.errorCount = 0
def testStarted(self):
self.runCount = self.runCount + 1
def testFailed(self):
self.errorCount = self.errorCount + 1
def summary(self):
return "%d run, %d failed" % (self.runCount, self.errorCount)
class TestCase:
def __init__(self, name):
self.name = name
def setUp(self):
pass
def tearDown(self):
pass
def run(self):
result = TestResult()
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
self.tearDown()
return result
class WasRun:
def __init__(self, name):
self.name = name
self.wasRun = None
self.log = ""
def setUp(self):
self.wasRun = None
self.log = "setUp "
def testMethod(self):
self.wasRun = 1
self.log = self.log + "testMethod"
def tearDown(self):
self.log = self.log + " tearDown"
def testBrokenMethod(self):
raise Exception
class TestCaseTest(TestCase):
def testFailedResultFormatting(self):
result = TestResult()
result.testStarted()
result.testFailed()
assert "1 run, 1 failed" == result.summary()
本章小结
回顾本章内容,我们完成了以下关键任务:
- 编写了粒度更小的测试方法testFailedResultFormatting,用于验证测试失败结果的正确性。
- 更新了TestResult类,使其能够准确记录测试运行次数和失败次数,并正确格式化测试结果。
- 修改了TestCase类的run方法,实现了对测试方法异常的捕获和测试失败的记录。
- 注意到setUp()方法中可能存在的异常捕获问题,并将其列入待办事项。
通过对未通过测试用例处理的研究,我们的xUnit测试框架在功能完善的道路上又前进了一步。后续,我们将着手解决待办事项,并探索如何让多个测试一起运行,持续提升测试框架的实用性和稳定性。