第23章 走进xUnit:测试驱动开发的关键工具(测试套件的奥秘)

写在前面


这本书是我们老板推荐过的,我在《价值心法》的推荐书单里也看到了它。用了一段时间 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测试框架的实践中又迈出了坚实的一步。后续,我们将继续探索如何进一步优化测试框架,解决遗留问题,提高测试的稳定性和效率,为软件开发提供更可靠的质量保障。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值