TDD(Testing Driven Development)介绍与实践

TDD(Testing Driven Development)介绍与实践

  • 作者:Geticsen
  • 时间:2020-08-22
  • 85人已阅读
简介 本文简洁的讲述了TDD与TDD的简单实践。以及为什么要用TDD,大多时候我们只是了解到了TDD但是在实际的开发中TDD是需要极其丰富的经验才能做到。

TDD是什么

    TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,TDD因为在编写实际的代码之前就已经写了测试所以在实际的开发中使用会提前建立起一个测试防护网,这些测试包含了单元测试以及集成测试,在TDD的实践中代码的安全得到保证。

TDD与普通开发的区别

传统开发模式:

  先写代码后写测试,这就是传统比较正常的编程方式,这样的开发方式符合我们的思考方式,在编程中不断修改不符合的代码直到基本符合要求然后就可以完成最后的功能了。

TDD开发模式:

 先写测试后写实际实现代码,测试在前实现在后,需要注意的是在测试之前要明确要求做好任务分解然后才是编写测试最后编码实现所写的测试用例,当测试用例太多的时候还需要用到等价类划分来对测试用例的数目进行缩减。

我们为什么要用TDD

  从实际的开发中其实传统模式更接近于我们正常人的思考模式,TDD的开发是反过来的我们必须要先写测试后实际编码,这样的难度在于我们必须知道我们每一个功能实际应该是怎样的,那既然是这样为啥要用……TDD开发呢?这里稀疏总结一下有如下几个原因。

TDD可以帮助我们构建测试防护网

  因为我们所有的代码都是围绕在我们编写的测试之下去完成的所以所有的代码都在我们的测试的防护之下,这就是测试防护网,测试防护网保证了代码逻辑的安全性。

TDD的思考模式使得开发人员更了解功能

  在写测试之前开发者必须知道条件与结果才能写出一个测试,这样的测试就要求开发者明确输入与输出,输入输出的明确保证了开发者的开发过程,反过来也要求开发者必须知道自己所写代码到底实现了什么。

TDD的开发模式提高了整体的代码质量

  因为明确的输入输出,所以在开发者实现过程中为了实现功能在很大程度上就不会引入与实现无关的代码,因为你所写的代码就是为了通过测试,没有无关代码的引入确保了代码的可靠性。

TDD以更小的代价发现以及修复bug

  我们知道在开发中bug的修复代价是随着开发流水线越往后修复成本越大,举个简单的例子:我们在单元测试发现了bug就修复了其代价肯定是远远小于上线之后去修复bug的。实际的bug修复的难度是:单元测试<集成测试<UI测试<端到端测试。

在传统开发中很容易漏写测试

  传统的开发中我们是先实现了业务功能然后再去写测试,这样有个很大的问题就是:程序已经写完再写测试无法完全考虑到足够的情况,程序开发者此时已经完成了功能即使存在潜藏的bug也会尽快完成程序的交付导致测试漏写。那这样有代码没有经过测试防护就会被提交。

简化设计不会过度设计

  过度设计存在于直接写实现代码的实现方式中。先写了测试就完成了设计你的实现代码只需要完成测试即可。

TDD的实践

TDD的前期工作

  我们要实践TDD是不能直接开始写测试的首先你的完成整个功能的理解,如果没有完成这一步我们的测试将不符合要求。另外在每次编写测试用例并编写完当前测试用例的实际实现代码之后要把所有的测试代码跑一边以防当前代码对之前代码的影响,如果有测试用例不通过你得重新修改代码直到所有代码全部通过全部测试才能提交代码到仓库。

在测试的时候我们用的测试结构是given,when,then具体的解释如下:

given:所给的条件

when:所做的动作

then:对得到的实际结果判断是否为正确的结果

在我们命名实际的测试函数名时需要用如下表意的方式进行:

should_xxxx_when_xxxx_given_xxxx()

例如下面的函数名表示,在输入(given)公司id与公司信息  执行更新公司的操作之后  应该返回更新后的公司

should_return_updated_company_when_update_company_given_company_id_and_company_info()


引入单元测试与集成测试的依赖

    testImplementation 'org.springframework.boot:spring-boot-starter-test:2.3.1.RELEASE'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2'
    testCompile "org.mockito:mockito-core:3.4.0"

单元测试

下面是单元测试的一个简单实例:

  @Test
  void should_return_company_when_add_company_given_company() {
//        given
        Company company = new Company(1,"oocl",1000);
        CompanyRepository companyRepository = mock(CompanyRepository.class);
        given(companyRepository.save(company)).willReturn(company);
        CompanyServiceImpl companyService = new CompanyServiceImpl(companyRepository);
//        when
        Company createdCompany = companyService.addCompany(company);
//        then
        assertEquals(company.getId(),createdCompany.getId());
    }

集成测试

下面是集成测试的一个简单实例:

 @Test
    void should_return_company_when_post_company_given_company() throws Exception {
//        given
        String companyString = "{\n" +
                "\"id\": 2,\n" +
                "\"Employees\": null,\n" +
                "\"companyName\": \"dz\",\n" +
                "\"employeesNumber\": 1000\n" +
                "}";
//        when then
        mockMvc.perform(post("/companies")
                .contentType(MediaType.APPLICATION_JSON)
                .content(companyString))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.companyName").value("dz"))
                .andExpect(jsonPath("$.employeesNumber").value(1000));
    }



文章评论

Top