01, preface,

It’s been a long time since you’ve seen these three big letters on the Internet. TDD stands for Test Driven Development — it sounds like a pretty good idea.

The idea is to ensure two things:

Make sure all needs are taken care of.

As the code is added and refactored, you can check that all functionality is correct.

But then I didn’t hear from TDD again for a long time. Some people say that TDD is dead and offer the following comments:

1) In general, developers should not write code without failing test cases — this seems reasonable, but it can lead to excessive testing. For example, you can’t help but write four lines of test code to make sure one line of production code is correct, which means that if one line of production code needs to be changed, you have to change the four lines of test code.

2) It is easy to fall into the trap of writing code to comply with TDD: the code is for testing purposes and ignores actual requirements.

02. What exactly is TDD?

Whether OR not TDD is dead, let’s review what TDD really is.

The basic idea of TDD is to write test code before developing functional code. That is to say, after a certain function is clearly developed, first think about how to test the function, and complete the writing of the test code, and then write the relevant code to meet these test cases. The loop then adds additional features until all features are developed.

The basic process of TDD can be broken down into the following six steps:

1) Analyze requirements and break them down into specific tasks.

2) Take a task from the task list and write a test case for it.

3) Since there is no actual functional code, the test code is unlikely to pass (red).

4) Write the corresponding functional code and let the test code pass as soon as possible (green).

5) Refactor the code and ensure that the test passes (refactor).

6) Repeat the above steps.

The above process can be illustrated in the following figure.


03. Practice process of TDD

In general, we tend to write functional code as soon as possible after the requirements analysis is complete, and then call and test it later.

TDD, on the other hand, assumes that we already have a “test user” who is the first user of the functionality code, even if the functionality code is incomplete.

When writing test code from the perspective of the “test user”, we need to think about how the “test user” will use the functional code. Do you call a method directly from a class (static methods), or do you build an instance of the class to call a method (instance methods)? How does this method pass parameters? How do you name the method? Does the method return a value?

Once we had the test code, we started writing the feature code, and we had to go from “red” to “green” as fast as we could. The feature code might be pretty inelegant, but that’s okay.

Once the test passes, it’s safe to “refactor” the functional code — optimizing the ugly, bloated, performance-biased code.

Next, suppose we receive a development requirement:

The barking team to the small town adventure island performance, tickets for 99 yuan, adventure island only a programmer Wang two need to develop a small program can calculate ticket income.

According to the TDD process, Wang Er needs to write a simple test case using Junit first, and the test expectation is: the income of selling a ticket is 99 yuan.

  


In order for the compilation to pass smoothly, king 2 needs a simple Ticket class:

  


The test case results are shown below, with red indicating that the test failed: the expected result is 99, and the actual result is 0.

  


= = = = = = = = = = = = = = = = = = = = = = = =

  


Run the test case again, and the result looks like the figure below, with green indicating that the test passed: the expected result is 99, but the actual result is 99.


Green, green, the test passed, it was time to refactor the functional code. 99 dollars is a magic number, at least declared constant, right?

  


Run the test case after the refactoring, and add a few more if the test passes, such as if ticket sales are negative, zero, or even a thousand.

  


When sales are negative, Wang wants the function code to throw an exception; When the sales volume is zero, the calculation result of the function code should be zero; For sales of a thousand, the result should be 99,000.

Rerun the test case and the result should look like this:

  


There are two test cases that fail, so Wang ii needs to continue to modify the functional code, and adjust it as follows:

  


Run the test case again and it passes. It’s time to refactor again. When sales are zero, or greater than or equal to one, the code can be merged, and the refactoring results in the following:

  


After the refactoring is complete, run the test cases to ensure that the refactored code is still usable.

04, finally

The following conclusions can be drawn from the above practice process:

What TDD wants to do is give us confidence in our code because we can test it to see if it’s correct.

That said, a key part of the TDD process is how to write effective test code, and there are four principles to follow:

1) The test process should try to simulate the process of normal use.

2) Branch coverage should be done as much as possible.

3) Test data should include real data as well as boundary data.

4) Test statements and test data should be as simple and easy to understand as possible.

Note that these four principles apply not only to TDD, but also to unit testing in any process.

In the end, DEAD or not, TDD is not a silver bullet and may not fit every scenario, but that shouldn’t be a reason to reject it.