美文网首页码农的世界Java 杂谈
为什么那些人选择TDD这种方式开发?

为什么那些人选择TDD这种方式开发?

作者: PageThinker | 来源:发表于2019-08-12 19:09 被阅读76次

    你是否听到过TDD(Test Driven Development)这种开发方式?是否身边人坚持使用这种开发方式呢?你是否也体会到TDD所带来的那部分好处呢?


    TDD

    《在开发人员如何拥抱?》中,并没有提必须要用TDD的方式,不过TDD有其自身的好处,我自身也是TDD的实践者。

    TDD指的是 Test Driven Development --- 测试驱动开发。通过测试代码来驱动出业务带的实现。是一九九几年哪个时间段通过实践总结出来的一种然间开发方式,TDD的目的:聚焦价值,让程序代码成为易于修改的代码。

    我是一名TDD的实践者,对TDD也有一个自己学习和理解的过程。从刚开始不清楚为什么这么做,到后来一切围绕商业价值后实践TDD的摸索,逐渐体会了TDD这种实践方式的用意。

    1. 为什么使用UT来驱动出业务代码?

    TDD是通过单元测试来驱动业务代码的实现。所以我们先回顾下单元测试能够带给我什么?

    1. 单元测试是一种验证行为;
    2. 单元测试避免了相当数量的反馈循环,尤其是功能验证方面的反馈循环。
    3. 单元测试也是一种编写文档的行为;
    4. 单元测试更是一种设计行为;

    1.1 单元测试是一种验证行为

    单元测试是一种验证行为,这个或许是很多人脑子中对单元测试唯一的定位。因为通过单元测试确实能够在一定程度上验证实现代码在特定情况下是否按照期望来运行相关逻辑。

    1.2 单元测试避免相当数量的反馈循环

    而且单元测试避免相当数量的反馈循环,尤其是功能验证方面的反馈循环。在开发开发中我们发现很多开发者在验证自己开发出来的功能时,会采用下面两种方案:

    1. 不测,凭直觉写完业务逻辑,直接丢给测试人员。
    2. 测,但是得通过UI界面上的操作,或者网络请求工具,或者临时编写个main函数再注释掉的方式来验证自己实现的业务代码。

    第一种方法肯定是不可取的,因为反馈循环的链条最长,需要等待测试人员测试,测试人员给出反馈时需要讲清楚上下文,并找到稳定复现的方法,并通过文字记录、语言表达等途径来表达给开发人员问题,开发人员还需要从当前做的事情的上下文中切换到出现问题的上下文中,对软件修修补补。在不考虑沟通效率的前提下,这个反馈循环是最长的,成本最高的。

    第二种,虽然开发人员进行测试,测试的目标代码和其他环境的依赖太多了。比如只需要验证某一行代码,却要从网络请求开始。使用main函数确认临时测试了代码,但是也给代码带来了坏味道,注释的代码在后续并没有什么约束的作用,即使通过口口相传的方式建立了约束,那么想想这样的反馈高效吗?稳定吗?

    通过单元测试来验证业务代码,其实是一个一劳永逸的行为。不但验证了当前的目标的代码,同时也留下了约束,来描述当前某段代码的正确行为是什么。

    1.3 单元测试也是一种编写文档的行为

    说单元测试是一种编写文档的行为,其实需要建立在一定的前提下,因为代码本身就不适合做阅读,在没有良好可读性代码的时候,那单元测试做文档更是一种痛苦。

    什么情况下单元测试能够作为一个好的文档呢?

    一定的约束和纪律性能给团队带来更高的效率。

    UT文档化需要分两步

    1. UT和业务描述建立关系
    1. UT自身的描述

    我们平时肯定会接触到Feature、UserStory、Task、Bug等,现在又加上UT,如何让UT来为我们带来文档的作用呢?可以通过索引来实现,我们按照下面的格式为UserStory,Feature建立索引:

    F=Feature
    S=User Story
    T=Task
    

    然后为其加上序号

    F01 S01 T01
    或者
    F01 S01
    

    这样每一个测试方法通过索引都能够和业务文档上Task或者User Story的描述建立关系。

    UT自身的描述:指的是通过简单的规则约束来让UT更加易读易理解。BDD给我们提供了一种很好的描述方法:given、when、then。

    Given:在什么亲体条件下
    When:发生什么事情是
    Then:结果是什么

    为了使UT更加易读,我们让测试方法名和测试方法块都按照given、when、then的方式来组织。

    下面是方法名的建议:

    should...when...given...
    

    下面是方法块的建议:

    public void should_response_a_ticket_when_parking_car_given_parking_lot_with_3_space_and_a_car() {
        ParkingLot parkingLot = new ParkingLot(3);
        Car car = new Car();
    
        Ticket ticket = parkingLot.parking(car);
    
        assertThat(ticket).isNotNull();
    }
    

    上看的这个方法中,通过Given、When、Then的结构来代码结构,中间使用空行分隔。

    下面是个实际的例子:

    
    F01:作为一个普通用户,我通过快递柜,存快递和取快递。
    
    S01:作为一个普通用户,我可以通过快递柜屏幕上的按钮,选择快递柜箱子的大小后,将快递物品放入到快递柜中。
    
    AC01: 
      given:一个普通用户,一个有空位的快递柜;
      when:当点击存件按钮
      Then:屏幕显示打开的快对柜箱的位置,并打开该快递柜的门,
    

    下面是对象的测试代码:

    public void should_open_the_door_when_click_post_button_given_a_express_box_lot_with_3_space_and () {
        ...
    }
    
    public void should_not_open_the_door_when_click_post_button_given_a_express_box_lot_with_0_space_and () {
        ...
    }
    

    通过Test Case我们能够清楚到找到业务上下文,并通过 Test Case的方法名和方法体结构清晰的只要在测试什么内容。

    1.4 单元测试是一种设计行为

    单元测试是一种设计行为,这是单元测试让我始终受益匪浅的地方,因为测试先行的方式让我在实现业务代码的时候就是两件事:聚焦、让代码易测试。

    后者较容易理解,因为易测试代表了在一定程度上代码已经实现了解耦。你可能会举出一些例子或者场景来反驳,其实想清楚要测试什么,哪些是这次测试不需要的但是又被迫加进来的,分清楚要测的和增加干扰的信息其实就意味着我们能够对代码进行再次的重构和解耦来让代码更加容易测试。

    上面也体现了一部分聚焦,但是还有更多可以聚焦的内容。比如:

    先写测试时需要先组织测试条件和验证条件,最后写实现。因为then的约束条件,我们每次只需要聚焦在一小块代码实现上,即使在代码实现上遇到了依赖的时候,我们可一直在依赖上返回期望的结果,并逐渐一个或多个test case方便我们管理好test case清单,并聚焦在当前实现上。

    因为每次都是聚焦一小块业务代码的实现,对一小块代码进行添加、修改所以更容易划分清楚职责。

    应对多出的细节设计,最终我们聚焦在代码级的设计上,让代码更加易读、提高。如果你能够识别出Bad smell,熟悉设计原则和设计模式,那么生成的代码更加易维护。

    2. TDD时对代码的修改行为往往会重复下面的动作。

    TDD大致流程

    重复上面的代码非常重要,因为良好的代码往往不是一次生成的,而是通过不断重构生成的。

    重复面的过程其实是在提前review修改点,将codereview的反馈环在此变短。如果是初次上手TDD,可以聚焦在Simple Design上,如果是TDD的老手,设计原则、设计模式会让TDD事半功倍。

    关于如何《从Simple Design入手让代码易修改》,可以看看这篇文章。

    相关文章

      网友评论

        本文标题:为什么那些人选择TDD这种方式开发?

        本文链接:https://www.haomeiwen.com/subject/sfkpjctx.html