什么是测试驱动的开发(TDD)以及为什么重要
#发展 #tdd #测试 #java

我第一次听到测试驱动开发(TDD)时,这听起来对我来说很奇怪。我想到很多问题:

  • 我们如何根据测试方案制定代码?

  • 如何在实际功能代码之前对测试进行编码?

  • 它有效吗?

  • so s o o n of of。

也许,它一直在开发他们第一次克服TDD时发生。但是,随着时间的流逝,您可以对TDD进行大量实施,您会发现它的有益。在这篇简短的文章中,我将向您介绍TDD是什么,我们如何实施它以及它给我们带来了什么好处。此外,我将向您展示一个简单的示例,说明我们如何在Java中实现它。

什么是测试驱动的开发?

测试驱动开发(TDD)是经常在软件开发生命周期(SDLC)中使用的著名软件开发过程的列表。

测试驱动的点是开发过程依赖于在完全开发测试案例之前将软件要求转换为测试案例。然后,我们可以根据我们先前描述的测试案例跟踪开发过程。

红色,绿色,重构(RGR)

要在我们的项目中应用TDD,我们遵循红色,绿色,重构(RGR)方法。

RGR cycle

这种方法将帮助开发人员将重点分散在开发过程中。 RGR方法的每个阶段的含义是:

  • 红色

    • 定义要测试的方法的测试用例
    • 定义方法的行为/期望
    • 在此阶段,我们将在我们之前定义的测试案例中失败。在TDD过程中,这是正常的,因为我们以后进行修复。
  • 绿色

    • 实施必要的逻辑以使您的方法通过测试。
    • 目标只是通过测试!优化和代码效率以后出现。
  • 重构

    • 改善您的实施!
    • 在此阶段,不要在代码中添加新功能,只需优化。

此外,还有三个简单的规则从Uncle Bob应用TDD:

  1. ,除非要进行单元测试通行证,否则您不允许编写任何生产代码。

  2. 您不允许您编写比失败的更多单位测试。汇编失败是失败。

  3. 您不允许您编写比通过一个失败的单元测试更多的生产代码。

TDD的好处

通过实施TDD,您将获得诸如:

的好处

高码覆盖范围

TDD最有益的点是代码覆盖范围。我们的开发是由测试案例驱动的,因此这意味着我们宣布了代码中发生的所有行为,这意味着我们已经知道我们的代码在编码之前将如何行为。

定义明确的代码逻辑

在TDD中,我们迭代地改进了逻辑。从最简单的实现到更复杂的(我们方法实施的最终目标)。这也使我们更容易管理逻辑,因为我们将实现分为较小的零件。

在开发过程中早期预防虫子

通过提供测试,您可以确保所有代码都可以按预期工作。添加新功能或进行更新有时会导致违反您现有的代码。可以通过测试尽早发现这种违规行为。因此,在将有缺陷的代码推向生产之前,您将获得警告。

改善您的代码质量

通过测试驾驶,您已经知道您在过程开始时目标的规格,以便可以消除每个不必要的零件,并且您的代码干净。

简单示例

Fizzbuzz问题

在此示例中,我们将解决一个 fizzbuzz 问题。我相信你们中有些人已经熟悉这个问题,FizzBu​​zz问题是代码访谈的著名编码问题之一。 FizzBu​​zz问题的标准是:

  • 如果输入号被3分3,则打印“ Fizz”

  • 如果输入号被5分5,则打印“嗡嗡声”

  • 如果输入号被3和5排除,则打印“ FizzBu​​zz”

  • 除此之外,将输入打印为字符串。

空的实现

这是我们TDD的第一部分。我们创建一个具有最小实现方法的方法。

public class FizzBuzzConverter {
    public String convertNumberToFizzBuzz(int number) {
        return "";
    }
}

FizzBuzzConverter类中的convertNumberToFizzBuzz方法是我们将使用的方法来练习TDD实现。您可以看到,我们使用一个空字符串返回值初始化它。

第一个周期

我们将创建第一个测试案例,如下所示的代码

import org.junit.Assert;
import org.junit.Test;

public class FizzBuzzConverterTest {

    // First cycle test
    @Test
    public void givenNumberString_whenNotDivisibleByThreeOrFive() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("1", converter.convertNumberToFizzBuzz(1));
        Assert.assertEquals("2", converter.convertNumberToFizzBuzz(2));
    }
}

我们希望convertNumberToFizzBuzz方法返回每个整数输入的字符串。但是它将返回 错误 。这就是为什么我们需要像下面的代码那样修改convertNumberToFizzBuzz方法。

public class FizzBuzzConverter {
    public String convertNumberToFizzBuzz(int number) {
        return String.valueOf(number);
    }
}

此重构将为我们提供第一个周期测试方案的通行证。

第二个周期

我们迭代添加必要的测试方案。现在,我们必须在输入号码分组3时测试条件3。因此,我们应使用下面的代码更新测试类。

import org.junit.Assert;
import org.junit.Test;

public class FizzBuzzConverterTest {

    // First cycle test
    @Test
    public void givenNumberString_whenNotDivisibleByThreeOrFive() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("1", converter.convertNumberToFizzBuzz(1));
        Assert.assertEquals("2", converter.convertNumberToFizzBuzz(2));
    }

    // Second cycle test
    @Test
    public void givenFizz_whenDivisibleByThree() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("Fizz", converter.convertNumberToFizzBuzz(3));
        Assert.assertEquals("Fizz", converter.convertNumberToFizzBuzz(6));
    }
}

像第一个周期一样,它返回 错误 ,因为我们尚未在代码中实现此情况。因此,我们应该更新代码以获取此周期的绿色代码。更新如下所示。

public class FizzBuzzConverter {
    public String convertNumberToFizzBuzz(int number) {
        if (number % 3 == 0)
            return "Fizz";
        return String.valueOf(number);
    }
}

第三周期

就像第二个周期一样,现在我们将对下一个情况进行测试。我们应该添加一种新的测试方法来测试输入号当时的条件。

import org.junit.Assert;
import org.junit.Test;

public class FizzBuzzConverterTest {

    // First cycle test
    @Test
    public void givenNumberString_whenNotDivisibleByThreeOrFive() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("1", converter.convertNumberToFizzBuzz(1));
        Assert.assertEquals("2", converter.convertNumberToFizzBuzz(2));
    }

    // Second cycle test
    @Test
    public void givenFizz_whenDivisibleByThree() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("Fizz", converter.convertNumberToFizzBuzz(3));
        Assert.assertEquals("Fizz", converter.convertNumberToFizzBuzz(6));
    }

    // Third cycle test
    @Test
    public void givenBuzz_whenDivisibleByFive() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("Buzz", converter.convertNumberToFizzBuzz(5));
        Assert.assertEquals("Buzz", converter.convertNumberToFizzBuzz(10));
    }
}

错误 再次!现在是扩展实施以涵盖方案的时候了。

public class FizzBuzzConverter {
    public String convertNumberToFizzBuzz(int number) {
        if (number % 3 == 0)
            return "Fizz";
        if (number % 5 == 0)
            return "Buzz";
        return String.valueOf(number);
    }
}

以上代码应该让您通过所有三种情况。

第四周期

别忘了,我们还有1个场景。我们必须测试该方法是否会返回“ FizzBu​​zz”,如果输入可以分别为3和5。

import org.junit.Assert;
import org.junit.Test;

public class FizzBuzzConverterTest {

    // First cycle test
    @Test
    public void givenNumberString_whenNotDivisibleByThreeOrFive() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("1", converter.convertNumberToFizzBuzz(1));
        Assert.assertEquals("2", converter.convertNumberToFizzBuzz(2));
    }

    // Second cycle test
    @Test
    public void givenFizz_whenDivisibleByThree() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("Fizz", converter.convertNumberToFizzBuzz(3));
        Assert.assertEquals("Fizz", converter.convertNumberToFizzBuzz(6));
    }

    // Third cycle test
    @Test
    public void givenBuzz_whenDivisibleByFive() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("Buzz", converter.convertNumberToFizzBuzz(5));
        Assert.assertEquals("Buzz", converter.convertNumberToFizzBuzz(10));
    }

    // Fourth cycle test
    @Test
    public void givenBuzz_whenDivisibleByThreeANdFive() {
        FizzBuzzConverter converter = new FizzBuzzConverter();

        Assert.assertEquals("FizzBuzz", converter.convertNumberToFizzBuzz(15));
        Assert.assertEquals("FizzBuzz", converter.convertNumberToFizzBuzz(30));
    }
}

再次 错误 再次。因此,我们必须对我们的代码进行增强。

public class FizzBuzzConverter {
    public String convertNumberToFizzBuzz(int number) {
        if (number % 15 == 0)
            return "FizzBuzz";
        if (number % 3 == 0)
            return "Fizz";
        if (number % 5 == 0)
            return "Buzz";
        return String.valueOf(number);
    }
}

作为3和5的可除外,也意味着在3x5上可以分组,我们可以应用上述改进。

重构

public class FizzBuzzConverter {
    public String convertNumberToFizzBuzz(int number) {
        StringBuilder result = new StringBuilder();

        if (number % 3 == 0)
            result.append("Fizz");
        if (number % 5 == 0)
            result.append("Buzz");

        return result.length() > 0 ? result.toString() : String.valueOf(number);
    }
}

最后,我们可以在通过所有测试用例后重构。我们进行重构以使convertNumberToFizzBuzz方法更加优雅。

改善您的测试代码

我们可以做几件事来改进我们的测试代码,例如:

用beses-act-assert(AAA)模式构建您的代码

AAA模式是一种结构测试用例的描述性和意图的方法。它描述了每个测试功能内部的操作顺序:

  • 安排:包含测试的设置逻辑。我们做对象初始化并准备正在测试的系统(SUT)的执行。

  • ACT :调用我们将要测试的事物(方法或API)。

  • 断言:验证SUT的作用是按预期的。

这是我们测试代码改进的示例:

import org.junit.Assert;
import org.junit.Test;

public class FizzBuzzConverterTest {

    @Test
    public void givenNumberString_whenNotDivisibleByThreeOrFive() {
        // Arrange
        FizzBuzzConverter converter = new FizzBuzzConverter();

        // Act
        String result1 = converter.convertNumberToFizzBuzz(1);
        String result2 = converter.convertNumberToFizzBuzz(2);

        // Assert
        Assert.assertEquals("1", result1);
        Assert.assertEquals("2", result2);
    }

    @Test
    public void givenFizz_whenDivisibleByThree() {
        // Arrange
        FizzBuzzConverter converter = new FizzBuzzConverter();

        // Act
        String result1 = converter.convertNumberToFizzBuzz(3);
        String result2 = converter.convertNumberToFizzBuzz(6);

        // Assert
        Assert.assertEquals("Fizz", result1);
        Assert.assertEquals("Fizz", result2);
    }

    @Test
    public void givenBuzz_whenDivisibleByFive() {
        // Arrange
        FizzBuzzConverter converter = new FizzBuzzConverter();

        // Act
        String result1 = converter.convertNumberToFizzBuzz(5);
        String result2 = converter.convertNumberToFizzBuzz(10);

        // Assert
        Assert.assertEquals("Buzz", result1);
        Assert.assertEquals("Buzz", result2);
    }

    @Test
    public void givenBuzz_whenDivisibleByThreeAndFive() {
        // Arrange
        FizzBuzzConverter converter = new FizzBuzzConverter();

        // Act
        String result1 = converter.convertNumberToFizzBuzz(15);
        String result2 = converter.convertNumberToFizzBuzz(30);

        // Assert
        Assert.assertEquals("FizzBuzz", result1);
        Assert.assertEquals("FizzBuzz", result2);
    }
}

拆除您的测试代码

使用之前的测试属性 beforeall aftereach 在您的测试代码上。您无需一次使用所有这些,只需选择哪些是必要的。

import org.junit.After;
import org.junit.Assert;
import org.junit.Test;

public class FizzBuzzConverterTest {
    private FizzBuzzConverter converter;

    // Setup method
    public void setup() {
        converter = new FizzBuzzConverter();
    }

    @After
    public void teardown() {
        converter = null;
    }

    @Test
    public void givenNumberString_whenNotDivisibleByThreeOrFive() {
        // Arrange
        setup();

        // Act
        String result1 = converter.convertNumberToFizzBuzz(1);
        String result2 = converter.convertNumberToFizzBuzz(2);

        // Assert
        Assert.assertEquals("1", result1);
        Assert.assertEquals("2", result2);
    }

    @Test
    public void givenFizz_whenDivisibleByThree() {
        // Arrange
        setup();

        // Act
        String result1 = converter.convertNumberToFizzBuzz(3);
        String result2 = converter.convertNumberToFizzBuzz(6);

        // Assert
        Assert.assertEquals("Fizz", result1);
        Assert.assertEquals("Fizz", result2);
    }

    @Test
    public void givenBuzz_whenDivisibleByFive() {
        // Arrange
        setup();

        // Act
        String result1 = converter.convertNumberToFizzBuzz(5);
        String result2 = converter.convertNumberToFizzBuzz(10);

        // Assert
        Assert.assertEquals("Buzz", result1);
        Assert.assertEquals("Buzz", result2);
    }

    @Test
    public void givenBuzz_whenDivisibleByThreeAndFive() {
        // Arrange
        setup();

        // Act
        String result1 = converter.convertNumberToFizzBuzz(15);
        String result2 = converter.convertNumberToFizzBuzz(30);

        // Assert
        Assert.assertEquals("FizzBuzz", result1);
        Assert.assertEquals("FizzBuzz", result2);
    }
}

使用代码覆盖工具

代码覆盖范围是开发过程中的重要一点。拥有高码覆盖范围意味着您还可以通过误导性更改对代码具有很高的保护。您也可以根据代码覆盖范围的百分比检查代码的逻辑。如果使用Java,则可以使用Jacoco或Serenity。每个编程语言或框架都有其代码覆盖工具。

结论

在您的项目中实施测试驱动的开发(TDD)有很多好处。许多公司将TDD纳入其开发周期,并通过熟练实施,您可以大大提高价值主张。

其他资源