写在前面
这本书是我们老板推荐过的,我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后,我突然思考,对于测试开发工程师来说,什么才更有价值呢?如何让 AI 工具更好地辅助自己写代码,或许优质的单元测试是一个切入点。 就我个人而言,这本书确实很有帮助。第一次读的时候,很多细节我都不太懂,但将书中内容应用到工作中后,我受益匪浅。比如面对一些让人抓狂的代码设计时,书里的方法能让我逐步深入理解代码的逻辑与设计。 作为一名测试开发工程师,我想把学习这本书的经验分享给大家,希望能给大家带来帮助。因为现在工作中大多使用 Python 代码,所以我把书中JAVA案例都用 Python 代码进行了改写 。
在前面的博客中,我们围绕xUnit测试框架深入探讨了测试用例的执行、结果记录以及未通过用例的处理等内容。本篇博客将聚焦于测试套件(Test Suite)的相关知识,它能将多个测试组合起来一起运行,是提升测试效率和组织性的重要概念。
测试套件的需求背景
在之前的测试实践中,每次单独运行测试的方式显得较为零散。例如,在文件结尾处调用多个测试时,代码看起来凌乱不堪。我们期望能够将测试组合起来一并运行,这样不仅能提高测试效率,还能更好地管理测试用例。这就引出了测试套件的概念,我们希望创建一个TestSuite,将多个测试添加其中,然后通过运行TestSuite得到汇总性的测试结果。
测试套件的代码实现
定义TestSuite类
class TestSuite:
def __init__(self):
self.tests = []
def add(self, test):
self.tests.append(test)
def run(self, result):
for test in self.tests:
test.run(result)
return result
在上述代码中,__init__
方法初始化了一个空列表tests
,用于存储测试用例。add
方法用于向测试套件中添加测试用例,run
方法则遍历tests
列表,依次运行每个测试用例,并将测试结果汇总。
使用TestSuite的测试示例
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 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):
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
self.tearDown()
return result
class TestCaseTest(TestCase):
def testSuite(self):
suite = TestSuite()
suite.add(WasRun("testMethod"))
suite.add(WasRun("testBrokenMethod"))
result = TestResult()
suite.run(result)
assert "2 run, 1 failed" == result.summary()
在testSuite
方法中,我们创建了一个TestSuite实例,添加了两个测试用例(一个正常测试testMethod
,一个会抛出异常的测试testBrokenMethod
),然后运行测试套件,并验证最终的测试结果汇总是否符合预期。
修复测试用例以适应新的测试模式
之前的一些测试用例在新的测试模式下可能会失败,因为它们使用了以前无参数运行的接口。我们需要对这些测试用例进行修复,例如:
class TestCaseTest(TestCase):
def testTemplateMethod(self):
test = WasRun("testMethod")
result = TestResult()
test.run(result)
assert "setUp testMethod tearDown" == test.log
def testResult(self):
test = WasRun("testMethod")
result = TestResult()
test.run(result)
assert "1 run, 0 failed" == result.summary()
def testFailedResult(self):
test = WasRun("testBrokenMethod")
result = TestResult()
test.run(result)
assert "1 run, 1 failed" == result.summary()
def testFailedResultFormatting(self):
result = TestResult()
result.testStarted()
result.testFailed()
assert "1 run, 1 failed" == result.summary()
这些测试用例在调用run
方法时,显式地传入了TestResult
对象,以适应新的测试框架逻辑。
简化测试的尝试
我们可以通过在setUp
方法中创建TestResult
来简化测试,虽然这可能会降低一定的可读性:
class TestCaseTest(TestCase):
def setUp(self):
self.result = TestResult()
def testTemplateMethod(self):
test = WasRun("testMethod")
test.run(self.result)
assert "setUp testMethod tearDown" == test.log
def testResult(self):
test = WasRun("testMethod")
test.run(self.result)
assert "1 run, 0 failed" == self.result.summary()
def testFailedResult(self):
test = WasRun("testBrokenMethod")
test.run(self.result)
assert "1 run, 1 failed" == self.result.summary()
def testFailedResultFormatting(self):
self.result.testStarted()
self.result.testFailed()
assert "1 run, 1 failed" == self.result.summary()
def testSuite(self):
suite = TestSuite()
suite.add(WasRun("testMethod"))
suite.add(WasRun("testBrokenMethod"))
suite.run(self.result)
assert "2 run, 1 failed" == self.result.summary()
在这个版本的TestCaseTest
类中,setUp
方法为每个测试用例创建了一个TestResult
对象,后续的测试方法直接使用该对象进行测试运行和结果验证。
本章小结
回顾本章内容,我们完成了以下重要任务:
- 为TestSuite编写了测试,验证了其能够将多个测试组合并正确运行,得到汇总结果的功能。
- 编写了部分代码实现,虽然没有使所有测试完美运行起来,但对测试框架的工作方式进行了重要改进,改变了
run
方法的工作方式,使单个测试和测试组合能够以相同的方式工作。 - 分离了共用的设置代码,通过在
setUp
方法中创建TestResult
对象等方式,简化了部分测试代码。
通过引入测试套件,我们在xUnit测试框架的实践中又迈出了坚实的一步。后续,我们将继续探索如何进一步优化测试框架,解决遗留问题,提高测试的稳定性和效率,为软件开发提供更可靠的质量保障。