Tags | TDD Java

Word | 2728 words

Note: This SERIES of TDD cases is mainly to consolidate and record my thinking and summary in the process of TDD practice. In my opinion, TDD itself is not difficult. Most of the difficulty lies in non-programming skills, such as analytical ability, design ability, presentation ability and communication ability. Therefore, in the process of TDD, I think TDD can train one’s habit and quality of thinking in advance, simplifying complexity, making plans and striving for perfection. The source code of this article is on personal Github, and the case requirements come from the Internet.

Target profit

  1. Be familiar with TDD process.
  2. Identify bad Code flavors and deploring Code refactoring practices.
  3. Learn about the java8 feature lambda and the use of partial functional interfaces.
  4. Get satisfactory test coverage.
  5. Improve your code confidence and refactoring courage.

Task review

  • Count off the students.
    1. If it is a multiple of the first special number, say Fizz.
    2. If it’s a multiple of the second particular number, report Buzz.
    3. If it is a multiple of the third special number, Whizz is called (current task).
    4. If it is a multiple of multiple special digits at the same time, add the corresponding words in the order of the special digits, for example, FizzBuzz, BuzzWhizz, and FizzBuzzWhizz.
    5. If the first special number is included, only Fizz is reported (ignoring rules 1, 2, 3, 4).
    6. If it is not a multiple of a special digit and does not contain the first special digit, the corresponding ordinal number is reported.

Code review

public class Student {

    public static String countOff(Integer position, List<GameRule> gameRules) {
        if (position % gameRules.get(0).getNumber() == 0) {
            return gameRules.get(0).getTerm();
        } else if (position % gameRules.get(1).getNumber() == 0) {
            return gameRules.get(1).getTerm();
        } else if (position % gameRules.get(2).getNumber() == 0) {
            return gameRules.get(2).getTerm();
        }
        returnposition.toString(); }}Copy the code

Test-driven development

If there is any repetitive logic or unexplained code, refactoring can eliminate duplication and improve presentation (reduce coupling, increase cohesion).

Following the previous article, at this time we need to solve the bad taste in Code – Duplicated Code. The analysis found that the codes were only similar, but not identical, and the intention expressed by the codes was not clear. The Extract Method reconstruction Method could be used to solve this problem. The isMultiple Method was extracted to determine whether the serial number of the student was a multiple of a special number, so as to make the code intention clearer. Soon I was done with the initial refactoring:

public class Student {

    public static String countOff(Integer position, List<GameRule> gameRules) {
        if (isMultiple(position, gameRules.get(0).getNumber())) {
            return gameRules.get(0).getTerm();
        } else if (isMultiple(position, gameRules.get(1).getNumber())) {
            return gameRules.get(1).getTerm();
        } else if (isMultiple(position, gameRules.get(2).getNumber())) {
            return gameRules.get(2).getTerm();
        }
        return position.toString();
    }

    private static boolean isMultiple(Integer divisor, Integer dividend) {
        return divisor % dividend == 0; }}Copy the code

Running the automated test passed all the tests, but the value method was still a bit stupid. Then I changed the value method from the above to automatic value loop to reduce the error rate, and the code became more concise and the intention of the expression was clearer:

public static String countOff(Integer position, List<GameRule> gameRules) {
    for (GameRule gameRule : gameRules) {
        if (isMultiple(position, gameRule.getNumber())) {
            returngameRule.getTerm(); }}return position.toString();
}

private static boolean isMultiple(Integer divisor, Integer dividend) {
    return divisor % dividend == 0;
}
Copy the code

Run the tests again to verify that the refactoring introduces any new errors. If it doesn’t, you probably made some mistake during refactoring that needs to be fixed immediately and rerunked until all the tests pass.


  • If it is a multiple of multiple special digits at the same time, add the corresponding words in the order of the special digits, for example, FizzBuzz, BuzzWhizz, and FizzBuzzWhizz.

As you can see from the description, the fourth subtask is also very simple, and I soon wrote the corresponding unit test and drove out the concrete implementation:

public class StudentTest {
    private final List<GameRule> gameRules = Lists.list(
            new GameRule(3."Fizz"),
            new GameRule(5."Buzz"),
            new GameRule(7."Whizz")); .@Test
    public void should_return_fizzbuzz_when_just_a_multiple_of_the_first_number_and_second_number(a) {
        assertThat(Student.countOff(15, gameRules)).isEqualTo("FizzBuzz");
        assertThat(Student.countOff(45, gameRules)).isEqualTo("FizzBuzz");
    }

    @Test
    public void should_return_fizzwhizz_when_just_a_multiple_of_the_first_number_and_third_number(a) {
        assertThat(Student.countOff(21, gameRules)).isEqualTo("FizzWhizz");
        assertThat(Student.countOff(42, gameRules)).isEqualTo("FizzWhizz");
        assertThat(Student.countOff(63, gameRules)).isEqualTo("FizzWhizz");
    }

    @Test
    public void should_return_buzzwhizz_when_just_a_multiple_of_the_second_number_and_third_number(a) {
        assertThat(Student.countOff(35, gameRules)).isEqualTo("BuzzWhizz");
        assertThat(Student.countOff(70, gameRules)).isEqualTo("BuzzWhizz");
    }

    @Test
    public void should_return_fizzbuzzwhizz_when_at_the_same_time_is_a_multiple_of_the_three_number(a) {
        List<GameRule> gameRules = Lists.list(
                new GameRule(2."Fizz"),
                new GameRule(3."Buzz"),
                new GameRule(4."Whizz")); assertThat(Student.countOff(24, gameRules)).isEqualTo("FizzBuzzWhizz");
        assertThat(Student.countOff(48, gameRules)).isEqualTo("FizzBuzzWhizz");
        assertThat(Student.countOff(96, gameRules)).isEqualTo("FizzBuzzWhizz"); }}public class Student {
    public static String countOff(Integer position, List<GameRule> gameRules) {
    
        if (isMultiple(position, gameRules.get(0).getNumber()) 
                && isMultiple(position, gameRules.get(1).getNumber()) 
                && isMultiple(position, gameRules.get(2).getNumber())) {
            return gameRules.get(0).getTerm() + gameRules.get(1).getTerm() + gameRules.get(2).getTerm();
        } else if (isMultiple(position, gameRules.get(0).getNumber()) 
                && isMultiple(position, gameRules.get(1).getNumber())) {
            return gameRules.get(0).getTerm() + gameRules.get(1).getTerm();
        } else if (isMultiple(position, gameRules.get(0).getNumber()) 
                && isMultiple(position, gameRules.get(2).getNumber())) {
            return gameRules.get(0).getTerm() + gameRules.get(2).getTerm();
        } else if (isMultiple(position, gameRules.get(1).getNumber()) 
                && isMultiple(position, gameRules.get(2).getNumber())) {
            return gameRules.get(1).getTerm() + gameRules.get(2).getTerm();
        }
    
        for (GameRule gameRule : gameRules) {
            if (isMultiple(position, gameRule.getNumber())) {
                returngameRule.getTerm(); }}returnposition.toString(); }}Copy the code

I ran into two problems. First, FizzWhizz was missing in the description of the fourth sub-task, so I improved the task list first. The second is that I’m starting to smell familiar bad things in the code again, so with automated testing on the back of me, I’m starting to build up my confidence and solve the if else verbose problem:

public static String countOff(Integer position, List<GameRule> gameRules) {
    String terms = gameRules
            .stream()
            .filter(rule -> isMultiple(position, rule.getNumber()))
            .map(rule -> rule.getTerm())
            .reduce((t1, t2) -> t1 + t2)
            .orElse(null);
    if(terms ! =null) {
        return terms;
    }

    for (GameRule gameRule : gameRules) {
        if (isMultiple(position, gameRule.getNumber())) {
            returngameRule.getTerm(); }}return position.toString();
}
Copy the code

At this point, all the automated tests pass, and the analysis reveals that the following for loop has become redundant because it has been merged into the new code and can now be removed:

public static String countOff(Integer position, List<GameRule> gameRules) {
    String term = gameRules
            .stream()
            .filter(rule -> isMultiple(position, rule.getNumber()))
            .map(rule -> rule.getTerm())
            .reduce((t1, t2) -> t1 + t2)
            .orElse(position.toString());
    return term;
}
Copy the code

All the automated tests have passed. Here, I introduced lambel and functional interface features of Java 8. Functional programming enhances the semantics of the code at the level of code implementation and makes the code more concise.


  • Count off the students.
    1. If it is a multiple of the first special number, say Fizz.
    2. If it’s a multiple of the second particular number, report Buzz.
    3. If it is a multiple of the third special number, report Whizz(Current task).
    4. If it is a multiple of multiple special digits at the same time, add the corresponding words in the sequence of special digits, for example, FizzBuzz, FizzWhizz, BuzzWhizz, and FizzBuzzWhizz.
    5. If the first special number is included, only Fizz is reported (ignoring rules 1, 2, 3, 4).
    6. If it is not a multiple of a special digit and does not contain the first special digit, the corresponding ordinal number is reported.

Looking at the joy in my heart, the last sub-task is expected to be done in 2 minutes, and then you can cross out the core task of “student count”. As a result, I quickly wrote the corresponding unit test and drove the corresponding concrete implementation:

public class StudentTest {
    private final List<GameRule> gameRules = Lists.list(
            new GameRule(3, "Fizz"),
            new GameRule(5, "Buzz"),
            new GameRule(7, "Whizz")); . @Test public voidshould_return_fizz_when_included_the_first_number() {
        assertThat(Student.countOff(3, gameRules)).isEqualTo("Fizz");
        assertThat(Student.countOff(13, gameRules)).isEqualTo("Fizz");
        assertThat(Student.countOff(30, gameRules)).isEqualTo("Fizz");
        assertThat(Student.countOff(31, gameRules)).isEqualTo("Fizz");
    }
}

public class Student {

    public static String countOff(final Integer position, final List<GameRule> gameRules) {
        if (position.toString().contains(gameRules.get(0).getNumber().toString())) {
            return gameRules.get(0).getTerm();
        }
        String term = gameRules
                .stream()
                .filter(rule -> isMultiple(position, rule.getNumber()))
                .map(rule -> rule.getTerm())
                .reduce((t1, t2) -> t1 + t2)
                .orElse(position.toString());
        return term;
    }

    private static boolean isMultiple(Integer divisor, Integer dividend) {
        returndivisor % dividend == 0; }}Copy the code

Run automated unit tests:

The newly added unit test passed, but the execution of the other three unit tests failed. In this case, I subconsciously thought there was a BUG in the newly added code, because the test failed after I added the implementation code. Through the analysis, find that it is the last one is the highest priority task, and the failure of unit tests just part of the test sample data by the current conditions of subtasks solution is simple, it is good to delete the corresponding test code, now all unit tests run through, and complete the “student number off” task.

Knowledge: What emboldened developers to refactor code?

This is due to the core idea of TDD – unrunnable/runnable/reconfigurable. Such a mechanism ensures that there are enough unit tests to support code refactoring, and that small steps can be taken with confidence in small, continuous feedback, because we can trust the automated BUG scout to the “back”.

Discussion: Does the new code need to be optimized?

Some may feel that the new code added if(…) It’s a little long and the meaning of expression is not particularly clear. In fact, I also have a strong code cleanliness obsession (Virgo one), but I think the current rhythm is very good. If it needs to be optimized, I think it only needs to add appropriate comments to indicate the intent of the code. What do you think? I look forward to your suggestions.

Reflection: Has the program been better designed so far?

I think so, but it looks good for now, and we’ll think about it more when we introduce context and implement other tasks.

TDD results

To-do list:

  1. Initiate the game.
  2. Define the rules of the game.
  3. Name three non-repeating single-digit numbers.
  4. !!!!!!!!!Count off the students.
    • If it is a multiple of the first special number, say Fizz.
    • If it’s a multiple of the second particular number, report Buzz.
    • If it is a multiple of the third special number, report Whizz.
    • If it is a multiple of multiple special digits at the same time, add the corresponding words in the order of the special digits, for example, FizzBuzz, BuzzWhizz, and FizzBuzzWhizz.
    • If the first special number is included, only Fizz is quoted(Ignore rules 1, 2, 3, 4).
    • If it is not a multiple of a special digit and does not contain the first special digit, the corresponding ordinal number is reported.
  5. Verify the input parameter.

Test Report:

Test coverage:

So far nine unit tests have been written to drive the “Student count” feature, with nearly 100% test coverage (except for the Student constructor), completing the core functionality of the case. In this process the deepening understanding of TDD overall process through practice, gradually familiar with how to identify bad taste in your code, as well as some reconstruction technique, it is interesting to note before I have been think technology will only on the demand analysis and task decomposition will use the two stage, now in the process of programming is often used to analysis technology, harvest is good, Don’t forget that in the process, you become more confident and have the courage to write better code.

Read the series

  • Test Driven Development (TDD) Summary – Principles
  • TDD Practice -FizzFuzzWhizz (1)

The source code

Github.com/lynings/tdd…


Welcome to follow my wechat subscription number, I will continue to output more technical articles, I hope we can learn from each other.