[JAVA] Implement FizzBuzz problem in test-driven development (preparation)

The other day, I participated in Microsoft's developer event de: code2017 for the first time. It was very interesting to hear about a wide range of themes, such as how to manage and change legacy development sites from the latest technological trends.

Among them, the test-driven development of the ** FizzBuzz problem, which was the subject of live coding in the session DO03 Test-Driven Development in 50 Minutes Development ** was very impressive.

So what is a test (unit test) in the first place? While looking back from, I tried to practice it in my own way.

It seems that it will be longer if you follow the practice contents in order, so I will post it in two parts, ** Preparation ** and ** Practice **. This time, it is ** preparation **.

What is a unit test? </ I>

In a word

A unit test is a test to check whether a program works according to the specifications in small units such as classes and methods.

Unit test method

There are three main types:

  • __ Function confirmation test __ Make sure the program works according to the specifications
  • __ Control flow test __ Check if all statements and conditional branches are executed
  • __ Dataflow test __ Check if data (especially data that is shared with others) is defined, used, and released

Why unit tests are needed

There seems to be an idea that unit tests are useless [^ 1], but I feel that I need them in the following points.

  • Build quality from small parts to bottom up.

  • Facilitates operation check after refactoring and specification changes, and improves system maintainability.

What is test-driven development? </ I>

In a word

It is a development method that repeats the following three cycles for each function of the specification.

  1. Write test code
  2. Implement a program that passes the test
  3. Refactor

In the xUnit framework, this cycle is described from the color of the bar display when the test fails / succeeds. RED </ font>- GREEN </ font> --REFACTOR Also called.

Why Choose Test Driven Development

The actual choice of test-driven development depends on the project situation, but I think the following benefits apply to any project.

  • Because it is possible to realize a clean code that works (Clean code that works).
  • Because it is possible to reduce the rework of fixing the entire affected range of the bug that appeared in the test after the implementation is completed.
  • Because a highly maintainable design can be realized (easy to test = strong against changes and high maintainability ↔ difficult to test = there is room for design improvement)

Now let's get ready for ** Test Driven Development of FizzBuzz Problems **.

Preparing the operating environment </ i>

Environmental setting

Type Material Version
Language Java (JDK) 1.8.0_111
IDE Spring Tool Suite 3.8.2
Build Maven 3.3.9
Test JUnit 4.12

Creating a project

Follow the steps below to create a project.

  1. Launch STS (Spring Tool Suite).
  2. Right-click in Package Explorer-> New-> Maven Project
  3. Check "Create a simple project (skip archetype selection)" and click Next
  4. Enter any package name in Group Id and Artifact Id and click Finish

Make sure that pom.xml is created directly under the created project.

checkPom.JPG

pom.xml settings

Next, add the following settings to pom.xml to use JUnit.

pom.xml


<!--Excerpt of only the relevant part-->
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
Supplement

If the JRE library version is not 1.8, add the maven-compiler-plugin setting to pom.xml.

JavaVersion.JPG

pom.xml


<!--Excerpt of only the relevant part-->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
           </plugin>
        </plugins>
    </build>

FizzBuzz Problem Requirements Definition </ i>

Now that the environment is in place, it's time to define the requirements to implement.

FizzBuzz problem

Write a program that prints numbers from 1 to 100. However, if it is a multiple of 3, print "Fizz" instead of a number, if it is a multiple of 5, print "Buzz", and if it is a multiple of both 3 and 5, print "FizzBuzz".

It seems that it was originally an English-speaking word game.

Split functional requirements for FizzBuzz issues

From the above problem, we will extract the keywords, divide the requirements, and define the implementation part.

Extract keywords </ i>

Extract the part corresponding to the command or condition as a keyword. 要件抜き出し.png

  • Numbers from 1 to 100
  • Print
  • "Fizz" when it is a multiple of 3
  • "Buzz" when it is a multiple of 5
  • "FizzBuzz" for multiples of both 3 and 5

Define the part to implement </ i>

From the keywords, define the part to be implemented in the class under test. Now let's leave the printing function to the caller of the FizzBuzz class, and give the FizzBuzz class the ability to return the string that the caller wants to print.

  • Take a number as an argument
  • Returns a string ~~ Print ~~
  • Argument value
  • However, when it is a multiple of 3, "Fizz"
  • However, when it is a multiple of 5, "Buzz"
  • However, in the case of multiples of both 3 and 5, "FizzBuzz"
  • Arguments from 1 to 100

Determine the order of implementation </ i>

Which of the above features do you want to start with, even if you exclude the class entry and exit "take a number" and "return a string"? I think the question remains. I think it depends on the time and the case, but I decided the order from the following functions. [^ 2]

  • __ Exception-free __ "The argument is from 1 to 100" = "If the argument is 0 or less or 101 or more, an exception will occur", so make it last
  • __ No branch condition __ Priority is given to items that "however" does not arrive
  • __ Branch conditions are simpler __ "For multiples of both 3 and 5" has two conditions, so we'll do it later.
  • Meet the final requirements faster (see note)

So, we will test-driven development of the FizzBuzz class, which takes a number as an argument and returns a string, in the following order.

  1. Return the value taken as an argument as a character string
  2. However, if it is a multiple of 3, "Fizz" is returned.
  3. However, if it is a multiple of 5, "Buzz" is returned.
  4. However, if it is a multiple of both 3 and 5, it returns "FizzBuzz".
  5. Error if the argument is not a number from 1 to 100

JUnit operation check </ i>

First of all, I would like to write test code ... but before that, I need to make sure that JUnit is installed correctly.

Specifically, prepare the test class FizzBuzzTest.java, write an empty test program, and execute the test.

Creating FizzBuzzTest.java

First, prepare the test class according to the following procedure.

  1. Place the cursor on "src / __ test__ / java", right-click-> New-> Package
  2. Enter any package name in the Name field and click Finish
  3. Place the cursor on the created package, right-click-> click Class
  4. Enter FizzBuzzTest in the Name field and click Finish (other settings are left at their defaults)
CreateFizzBuzzTest.JPG

The project structure after creating FizzBuzzTest.java is as follows.

FizzBuzzTestProject.JPG

Create an empty test program

Next, implement an empty test method that does nothing in the created FizzBuzzTest.java.

FizzBuzzTest.java



import org.junit.Test;

public class FizzBuzzTest {
    @Test
public void JUnit operation test() {
        
    }
}

Test run

Hover over the JUnit behavior test method of the FizzBuzzTest class you created and right-click-> Run As-> JUnit Test to run the test.

Then you will see a green bar indicating that the test was successful, as shown in the figure below.

JUnit確認テスト.JPG

From this, you can see that JUnit is working as default and you are ready to use JUnit. On the other hand, if the test fails, you can discover flaws in the test environment, such as improper installation by Maven.


Now that you've confirmed that JUnit works, you're done with ** Preparation **.

Here, the final project structure and program contents are listed as "Conclusion comes first". [^ 3]

Project structure
Project.JPG
Test program

FizzBuzzTest.java


package com.example;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;

@RunWith(Enclosed.class)
public class FizzBuzzTest {

public static class argument is not a multiple of 3 and 5{
        FizzBuzz fizzbuzz = new FizzBuzz();

        @Test
public void Returns 1 if 1 is given as an argument() {
            assertEquals("1", fizzbuzz.response(1));
        }
    }
    
public static class A multiple of 3 arguments only{
        FizzBuzz fizzbuzz = new FizzBuzz();

        @Test
public void Returns Fizz with argument 3() {
            assertEquals("Fizz", fizzbuzz.response(3));
        }
    }

public static class A multiple of 5 arguments only{
        FizzBuzz fizzbuzz = new FizzBuzz();

        @Test
public void Returns Buzz with argument 5() {
            assertEquals("Buzz", fizzbuzz.response(5));
        }
    }

public static class arguments are multiples of 3 and 5{
        FizzBuzz fizzbuzz = new FizzBuzz();

        @Test
public void Returns FizzBuzz with argument 15() {
            assertEquals("FizzBuzz", fizzbuzz.response(15));
        }
    }
    
public static class argument is a valid boundary value{
        FizzBuzz fizzbuzz = new FizzBuzz();
        
        @Test
public void Returns 1 if argument 1 is given() {
            assertEquals("1", fizzbuzz.response(1));
        }

        @Test
public void Returns Buzz with 100 arguments() {
            assertEquals("Buzz", fizzbuzz.response(100));
        }
    }

public static class argument is invalid Boundary value{
        FizzBuzz fizzbuzz = new FizzBuzz();
        
        @Test(expected = IndexOutOfBoundsException.class)
public void error occurs if argument 0 is given() {
            fizzbuzz.response(0);
        }

        @Test(expected = IndexOutOfBoundsException.class)
public void error occurs if argument 101 is given() {
            fizzbuzz.response(101);
        }
    }
}
Program to be tested

FizzBuzz.java


public class FizzBuzz {

    public String response(int num) {
        StringBuilder result = new StringBuilder();
        
        if(num < 1 || num > 100) {
            throw new IndexOutOfBoundsException();
        }
        
        if(num % 3 == 0) {
            result.append("Fizz");
        }

        if(num % 5 == 0) {
            result.append("Buzz");
        }
        
        if(result.length() == 0) {
            result.append(String.valueOf(num));
        }

        return result.toString();
    }
}

Next time, I would like to post as ** Practice ** how I proceeded with the implementation of the following test class (FizzBuzzTest.java) and test target class (FizzBuzz.java).

reference


[^ 1]: I only read the summary, but the original story is [Why \ -Most \ -Unit \ -Testing \ -is \ -Waste \ .pdf](http://rbcs-us.com/ documents / Why-Most-Unit-Testing-is-Waste.pdf) It is written in.

[^ 2]: For example, in a movie reservation system that has multiple discount options according to user characteristics, if the final requirement is "apply the option that increases the discount rate", then from the user characteristic condition that the discount rate is high I will test it.

[^ 3]: There are some differences from the live coding in de: code that I referred to because it was the result of my own practice and some parts were omitted in de: code due to time constraints.