[JAVA] TDD training summary

It's ABAB ↑ ↓ BA. This article has almost no Elm component. In this article, I'd like to talk about the TDD (Test Driven Development) training I'm taking ** weekly ** in the mysterious group Zenelo, only about the basics of the TDD cycle and Baby Steps.

TDD cycle and baby step

First, let's talk about the premise of test-driven development. In test-driven development, we will proceed with development while going through the following cycle.

Red -> Green -> Refactor

Red

You will write the test that fails first. So what does this mean?

For the implementation code, implement the ** interface ** (the implementation is empty, but the type is clearly defined and can be called from the test). It should not be a compilation error.

class FizzBuzz {
    public static String fizzbuzz(int num) {
       return null;
    }
}

Then write the test code. This is equivalent to ** designing and defining requirements **. At this time, the test code should never be written according to the implementation **. ** **

public class FizzBuzzTest {
    @Test
public void The number 3 is fizz() {
        assertThat(FizzBuzz.fizzbuzz(3), is("fizz"));
    }
}

The test will of course fail. The important thing here is that ** Red ** points the way to what's next to be implemented and becomes a ** living design **.

FizzBuzzTest >The number 3 is fizz FAILED
    java.lang.AssertionError:
    Expected: is "fizz"
         but: was null
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.Assert.assertThat(Assert.java:923)
        at FizzBuzzTest.The number 3 is fizz(FizzBuzzTest.java:9)

Green

Green has a ** minimum ** implementation that will pass the test. At this time, if there is no mistake in the test code (design), do not tamper with it at all. Run the shortest distance to the road pointed to the implementation. For example, no matter how much you know the answer, don't write code like this: I will explain later why you should not write the answer.

public class FizzBuzz {

    public static String fizzbuzz(int num) {
        if(num % 3 == 0) {
            return "fizz";
        }
        else {
            return null;
        }
    }
}

The minimum implementation is as follows.

public class FizzBuzz {

    public static String fizzbuzz(int num) {
        return "fizz";
    }
}

Refactor

If there is nothing in particular, it will be skipped.

Red again

It's the next TDD cycle. The thing to do is the same. Write a failing test and point to the next path.

public class FizzBuzzTest {
    @Test
public void The number 3 is fizz() {
        assertThat(FizzBuzz.fizzbuzz(3), is("fizz"));
    }

    @Test
public void The number 5 is buzz() {
        assertThat(FizzBuzz.fizzbuzz(5), is("buzz"));
    }
}

Now it's a mistake because it's a man who returns "fizz" no matter what number comes.

FizzBuzzTest >The number 5 is buzz FAILED
    java.lang.AssertionError:
    Expected: is "buzz"
         but: was "fizz"
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.Assert.assertThat(Assert.java:923)
        at FizzBuzzTest.The number 5 is buzz(FizzBuzzTest.java:14)

Second time Green

Of course, don't write the answer suddenly as before. Keep in mind only the minimum implementation. Don't use your head. Consider going to the next cycle at a good tempo.

public class FizzBuzz {

    public static String fizzbuzz(int num) {
        if(num == 3) {
            return "fizz";
        } else {
            return "buzz";
        }
    }
}

Refactoring ignored again

It looks good ...

It's about time to understand Red3

Let's insert a multiple of 3 other than 3.

public class FizzBuzzTest {
    @Test
public void The number 3 is fizz() {
        assertThat(FizzBuzz.fizzbuzz(3), is("fizz"));
    }

    @Test
public void The number 6 is fizz() {
        assertThat(FizzBuzz.fizzbuzz(6), is("fizz"));
    }

    @Test
public void The number 5 is buzz() {
        assertThat(FizzBuzz.fizzbuzz(5), is("buzz"));
    }
}

It goes into the else clause and returns " buzz ", so it becomes Red.

FizzBuzzTest >The number 6 is fizz FAILED
    java.lang.AssertionError:
    Expected: is "fizz"
         but: was "buzz"
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.Assert.assertThat(Assert.java:923)
        at FizzBuzzTest.The number 6 is fizz(FizzBuzzTest.java:14)

Finally implemented Green

If you want to push the minimum implementation, you can endlessly write the following nasty code, but let's go back to the requirements and implement it.

public class FizzBuzz {

    public static String fizzbuzz(int num) {
        //As expected, this is messing around!
        if(num == 3 || num == 6){
            return "fizz";
        } else {
            return "buzz";
        }
    }
}

Yes. This is a straightforward implementation! Let's wonder why you shouldn't do this implementation suddenly here. Let's say you implement this in the case where num is 3. Then would you write a test when num is 6? Since human beings basically work toward those who are comfortable, it is likely that they will not write. Since FizzBuzz is the theme this time, we recognize that "this kind of thing you can't make a mistake", but human beings are creatures that make mistakes, and even simple implementations that are usually done tend to skip verification and make mistakes.

public class FizzBuzz {

    public static String fizzbuzz(int num) {
        if(num % 3 == 0) {
            return "fizz";
        } else {
            return "buzz";
        }
    }
}

I would like to stop the test cycle here and summarize it. The important point here is that by starting the test cycle with test first (Red first), you can prevent the implementation from leading and get the guarantee that the implementation is covered by the test, and the test will be insufficient. And it was very important to solve the same problem as Test First by doing Baby Steps, and to reduce the granularity of the problems that are confronted at once and increase the tempo of implementation (do not use brains). .. For those who have never done TDD before, switching mindsets is very important, so it is important to ** train daily **. No athlete can play an active role without practicing. Try to protect the TDD cycle from simple examples and get a feel for Baby Steps.

Test speed and quantity

Whether TDD is slow or not is often raised as an agenda item. Will the development speed be slowed down by writing the test? Let's consider the FizzBuzz example above. Implement FizzBuzz at once. Do you want to deliver it as it is? Probably not. I'm not sure if it's via the UI or the CLI, but you'll probably run it and check.

printf Do you want to debug? If so, maybe you should check it by the following procedure.

―― 3 is fizz --Next, 5 is buzz --Go back and 6 is fizz

That? You know something ... That's right. TDD is the same flow that you actually check the application. What's the difference? Execution confirmation is only ad hoc verification. When a change occurs in the code, the confirmation becomes meaningless and only "you" confirm it. Imagine this is a complex intertwining of features like a real product. Do you want TDD? Do you want to check manually?

Basically, I will continue to write about the amount of tests until my anxiety disappears. On top of that, the duplicate code will grow in the test code. In that case, you should try appropriate refactoring and Parameterized test.

Recommended Posts

TDD training summary
Summary
Object-oriented summary
Rspec, TDD (1)
ransack summary