Test-Driven Development/Test Driven Development/TDD
Test Cases/TestCase/TC
Design/Design
Refactoring/Refactoring
In fact, we only do two things: Make the code Work and make the code Clean. The former is to do the right thing, and the latter is to do the right thing. Think about it. In fact, all the work we do at ordinary times, except useless work and wrong work, is truly the right work and truly meaningful work. In fact, there are only two categories: adding features and improving design, TDD is produced in this principle. If your work is not what we think, (this means that you still have a third type of work that is correct and meaningful, or that what you want to do is fundamentally different from what we are talking about ), this tells us that you do not need TDD, or it is not applicable to TDD. If we happen to guess (this is an accident for me, but it is a result of hard work for masters like Kent Beck and Martin Fowler), congratulations, TDD may be a magic weapon for you to significantly improve your work efficiency. Don't trust people in the future. If you leave it alone, because any new technology, as long as it is a technology that fundamentally changes the way people behave, will inevitably make people who believe in it more and more confident, more and more people do not believe it. This is like learning to swim. The only way to learn to swim is to go swimming in person. This is even better than learning success. Even if you think of Carnegie or Dr. Hill's books as a stream, you cannot have a positive attitude. But after you have achieved a career with a positive attitude, you can no longer leave it. Believe me, TDD is the same! To try TDD, follow these steps:
Compile TestCase |
--> |
Implement TestCase |
--> |
Reconstruction |
(Determine the scope and objectives) |
|
(Added features) |
|
(Improved design) |
[Friendly reminder: a very important practice in Agile modeling is called: Prove it With Code. This idea coincides With TDD.]
"Attractive advantages 』
- Completed upon completion. It indicates that I can clearly see that my work is over, and the traditional method is hard to know when the coding process is over.
- The traditional method does not have the opportunity to fully and correctly understand and utilize code.
- Provide Sample for people who use your results, whether it is to use your source code or directly reuse your provided components.
- Development teams have reduced communication costs and increased mutual trust.
- The transition design is avoided.
- The system can be released together with a detailed test set to facilitate modification and expansion of future versions of the program.
- TDD gives us confidence that we can solve today's problems, tomorrow's problems will be solved tomorrow, and today we cannot solve tomorrow's problems, because tomorrow's problems have not yet appeared (without TestCase ), I will never write any code unless I have a TestCase. I don't have to worry about today's problems tomorrow, as long as I have a green light.
"Not obvious advantages 』
- The design role is escaped. Everyone is designing an agile development group.
- Code is in high quality most of the time, and the results are visible in 100% of the time.
- Since programmers who write and test code can be ensured to be the same, the cost of understanding the code is reduced.
- It makes outstanding contributions to reduce the nuances between documents and code and the bugs introduced by such differences.
- Establishing a balance between pre-Design and emergency design provides a reliable basis for you to determine which design should be done in advance and which design should be performed in the iteration.
"Controversial advantages 』
- In fact, the development efficiency is improved. Everyone who is using TDD and believes in TDD will believe this, but the viewer is different. Those who do not believe in TDD will even firmly oppose this. This is normal and the world is always like this.
- More bugs are found than traditional testing methods.
- It makes the debugging function of IDE meaningless, or it should be said that it avoids the headache of debugging and saves debugging time.
- It is always in a state of either programming or restructuring, and won't drive people crazy. (Two hats)
- Unit Testing is very interesting.
Compile TestCase |
--> |
Implement TestCase |
--> |
Reconstruction |
(Not running) |
|
(Runable) |
|
(Reconstruction) |
Procedure |
Products |
(1) quickly add a test case |
New TestCase |
(2) compile all the code. The test just written may not pass the compilation. |
Original TODO List |
(3) make as few changes as possible to allow compilation to pass |
Interface |
(4) run all tests and find that the latest test cannot be compiled |
-(Red Bar) |
(5) make as few changes as possible to make the test pass |
Implementation |
(6) run all tests to ensure that each of them can pass |
-(Green Bar) |
(7) refactor the code to eliminate repeated Designs |
Clean Code That Works |
[When to refactor?]
If you work in a software company, it means that you will all deal with the idea of improving code quality through refactoring all day long. This is not only the case for you, but also for most of your colleagues. However, when should we rebuild and under what circumstances? I believe that you and your colleagues may have many different opinions. The most common answer is "refactoring during reconstruction" and "refactoring when writing down ", and "reconstruction before the next iteration starts", or simply "reconstruction will not be performed if there is no time in the past. Rebuild the next time you have time ". As you have foreseen, all these ideas are misunderstandings about refactoring. Refactoring is not a tool for building software, nor a software design model, nor a link in the software development process, people who correctly understand refactoring should regard refactoring as a way of writing code, or a habit. refactoring may happen at all times. In TDD, all work except writing test cases and implementing test cases is refactoring. Therefore, no design can be implemented without refactoring. As for when to refactor, we should also look at it separately. There are three examples of my experience: refactoring code when implementing test cases and restructuring design when a feature is completed, after restructuring the product, remember to rebuild the test case.
[When to design?]
This question is more difficult to answer than the previous one. To be honest, I am often troubled by this question when developing software based on TDD, I always think that some problems should be fixed before writing test cases, and some problems should naturally occur during the process of adding one test case. Therefore, my suggestion is that the design timing should be determined by the developers themselves and should not be limited by the TDD method. However, it is not necessary to determine the timing of the design without prior confirmation, otherwise, your hands and feet may be bound.
[When will a new TestCase be added?]
When nothing is done. We usually think that if you want to add a new feature, first write a TestCase that cannot be passed; if you find a bug, first write a TestCase that cannot be passed; if you do not have anything at the moment and start from 0, please first write a TestCase that cannot pass. All work starts with a TestCase. In addition, it should be noted that some masters require that we only allow one TestCase to light up the red light each time. Other TestCase cannot be written before this TestCase has no Green. Such requirements can be considered as appropriate, however, it doesn't matter if multiple TestCase lights are red, and it does not violate the main spirit of TDD.
[How to Write TestCase?]
Writing Test cases involves two processes: using existing code and defining the execution results of the Code. Therefore, a TestCase should include two parts: scenario and assertion. The first time I wrote TestCase, I felt a lot uncomfortable, because everything you wrote was solving the problem. Now I'm not very familiar with asking questions, but don't worry, you are doing the right thing, and the most difficult thing in the world is not how to solve the problem, but ask the right question!
[Can TDD help me eliminate bugs?]
A: No! Never confuse "test" with "insect removal! "Insect removal" means that programmers can reduce the number of bugs through their own efforts (we should not say it better to eliminate the word bug ), "testing" means that programmers write code other than the product to ensure that the product works effectively. Although the test cases written by TDD provide a basis for finding bugs to a certain extent, in fact, it is impossible for software development based on TDD to find bugs Through TDD (think about the "completed at completion" we mentioned earlier, when our code is complete, all test cases are highlighted with a green light, and the bugs hidden in the code will not be exposed.
However, if you want to talk about the relationship between "test" and "insect removal", I believe there are still many things to talk about. For example, TDD actually reduces the number of bugs, raise the focus of searching for bug battles from the entire battlefield to the Code battlefield. Also, the most terrible thing about a bug is not the depth of hiding, but the depth of the sky. If you find a bug that is not easily discovered by users, you may not make any outstanding contributions to your work. However, if you find that the density or discretization of a bug in a piece of code is too high, congratulations! You should discard and rewrite this code. TDD avoids this situation, so it reduces the search for bugs to a new low level.
[Should I write TestCase for a Feature or TestCase for a class?]
Questions that are frequently asked by beginners. Although we can see from the TDD statement that TestCase should be compiled for a feature, why is the TestCase written by the famous TDD masters one-to-one correspondence with classes/methods? In order to explain this question, my colleagues and I have done a lot of experiments. Finally, we come to a conclusion that although I don't know if it is correct, if you have no answer, you can trust us.
Our research results show that we usually write test cases for features at the beginning of a feature development. If you find that this feature cannot be expressed using TestCase, please segment it, until you can write TestCase for your features. From here on it is the safest, and it won't lead to any major design mistakes. However, as you constantly rebuild code, rebuild TestCase, and constantly follow the TDD idea, when the product is released along with the test case set, you will inadvertently find that the reconstructed test cases may correspond to the classes/methods in the product one by one.
[When should I run all tests?]
Good Question! Masters asked us to run the test cases completely after each refactoring. This requirement is understandable because refactoring may change the structure or design of the entire code, leading to unpredictable consequences. But what if I am developing an ERP? It may take several hours to run a complete test case. Many refactoring tasks are implemented by tools, and the feasibility and prerequisites of this requirement are shaken. So I think in principle you can choose a few TestCase that you think may be affected by this refactoring to run, but if it takes several seconds to run the entire test package, do not mind if you follow the requirements of the master.
[When can I improve a TestCase?]
The added test cases or code after reconstruction lead to the loss of the original TestCase effect and become meaningless, or even lead to incorrect results. This is the best time to improve TestCase. But sometimes you will find that this only results in the design of the original TestCase being bloated or redundant. It doesn't matter as long as it is not ineffective, you still don't need to improve it. Remember, TestCase is not your product, it should not look good, it should not be too scientific, or even have no performance requirements, as long as it can fulfill its mission, it also proves the feasibility of "Writing Test Cases with Ctrl-C/Ctrl-V.
However, the idea of Americans is actually not the same as that of us. Take the MindMap of Tony Barzan as an example. In fact, painting MindMap is just to express your own ideas or remember some important things, however, Tony suggested that you draw MindMap as a work of art, and even many artists use their own abstract MindMap to help Tony publicize it. Likewise, Masters asked us to write TestCase with the same quality as the code. But what I want to say is, how many domestic companies can write the product code well ?? Let's take a step by step.
[Why can't I pass the original test case now?]
This is an Alert, Red Alert! It may express two meanings-none of which are good meanings-1) the refactoring you just made may fail, or some errors have not been found, at least the reconstruction result is not equivalent to the original code. 2) The meaning of the added TestCase conflicts with the existing TestCase. That is to say, the new function violates the existing design, in most cases, the previous design is wrong. However, no matter what the error is, whether it is the meaning of that layer, it is more difficult to find the root cause of this problem than the normal operation of TDD.
[How do I know if there is a method or a class?]
This problem often occurs in my mind, whether you are the first to contact TDD or have become a TDD expert, it will be around you. However, the answer to the question can be referred to the previous section "when to design". The answer is not the only one. In fact, most of the time you don't have to consider the future. Today we only do today's things. As long as there are refactoring tools, it is easy to choose from methods to classes and from classes to methods.
[I want to write a TestCase, but I don't know where to start.]
From the very beginning, what matters most? Start from your feet, start from your work at hand, and start from your eyes. Starting with a core feature that does not have a UI, starting with an algorithm, or from a module that is most likely to delay time, starting with the most serious bug. This is one of the commonalities between the TDD and the mouse, and the difference is that the former has long been very powerful.
[Why does my test always look silly?]
Oh? Yes? Come on, give me a hand, too! Don't worry about this. In fact, the example given by the Masters is quite stupid. For example, an extreme example is to write a method for adding two int variables. The masters assert that 2 + 3 = 5, I want to assert 5 + 5 = 10 again. Isn't the code stupid? In fact, this is just an extreme example. When you first get started with TDD, it is no good to write such code. When you are skilled in it, you will find that this writing is unnecessary. Remember, modesty is the only way to TDD! Switching from a classic development method to TDD is just as difficult as switching from process-oriented to object-oriented. you may understand everything, but the class you write is not pure OO! My colleagues also told me that the real Taijiquan is fast, and it is not slower than any other quick boxing, but it is usually the first 10 years of studying taijiquan) it is too difficult to correct each posture, so you can only take it slowly.
[Where is TDD not applicable?]
Q: It is true that TDD is not suitable for many occasions. For example, military or scientific research products that require extremely high software quality-Shenzhou 6th, human destiny software-medical equipment, and so on. For example, software that is very important to design must be prepared in advance, these are not suitable for TDD, but they are not suitable for TDD. It does not mean that TestCase cannot be written, but the roles and status are different.
[Smiling faces compilation errors]
In the student age, compilation errors are the most feared. compilation errors may be considered by teachers as evidence of not attending classes seriously, or the weights of ridicule between students. Even older programmers who have been away from school for many years are still afraid of it, just as they are afraid of being late. In the subconscious, it seems that compilation errors are very likely to be linked to wages (or linked to IQ, which is not a good thing anyway ). In fact, as long as the Code submitted to version management has no compilation errors, do not worry about the compilation errors of your own code. Generally, compilation errors are concentrated in the following three aspects:
(1) Your code has a low-level error.
(2) The tested Code cannot be compiled because some Interface implementations do not exist.
(3) The test code cannot be compiled because some code does not exist.
Note that the second and third points are completely different. The former indicates that the design already exists and the implementation does not exist, resulting in compilation errors. The latter indicates that only TestCase is used, and nothing else exists, the design and Implementation do not exist, and there is no Interface or Implementation.
In addition, the compiler has another advantage, that is, to tell you the mistakes in your code with the most agile skills. Of course, if you have Eclipse, You can prompt compilation errors in a timely manner, and you do not need this function.
[Pay attention to your plan list]
In the case of non-TDD, especially the traditional waterfall model, programmers do not know what to do. In fact, there is always a design or something else to guide programmers in development. But in TDD, this advantage is lost, so a plan list is very important to you, because you must discover what to do. People of different personalities may have different responses to this issue. I believe that people who do things normally have no plans to rely on other people to make arrangements (the so-called generals) may be slightly uncomfortable, but it doesn't matter, tasks and Calendar (also known as efficiency manuals) have long been essential tools for modern office workers. They usually work and live in a planned way, such as me :), you will prefer this method that you can control the Plan.
[Deprecated daily code quality check]
If I remember correctly, PSP has strict requirements for personal code check, but it is also for personal issues. TDD recommends that you decode the daily code quality check, don't worry, because you are always doing things that TestCase requires you to do, and there is always a way (automatically) to check whether the Code has done these things-the red light stops the green light, therefore, the daily code check time may be saved. For a strict PSP practitioner, this cost is still considerable!
In addition, another benefit of daily code quality check is to help you understand your code and comprehensively examine your achievements from a macro, micro, and various perspectives. Now, when you follow TDD, this advantage is not needed. Do you still remember the second advantage of TDD mentioned earlier, because you have used your code comprehensively, this completely achieves the goal.
However, the problem is often not that simple. Can anyone tell me how I can fully examine the test cases I have written? Don't forget, they also exist in the form of code. Well, I hope this problem doesn't scare you, because I believe it is not a bottleneck so far, in addition, when writing product code, you will still find many issues that are not taken into account in the test code. You can modify the code here. The truth is that there is no way in the world to replace the process of thinking, so there is no way to prevent you from making mistakes. TDD only makes it easier for you to discover these mistakes.
[If you cannot complete a big test, start with the smallest one]
What can I do if I cannot start? There is a good example in the textbook: I want to write a category for a movie list. I don't know how to get started or how to write test cases. It doesn't matter, first, imagine the static results. If my movie list was just created, it should be empty. OK, just write this assertion, asserted that a previously initialized movie list is empty. This is not stupid. This is the details. The Olympic five-all-powerful gold medalist, Marilyn Jin, said: "What successful people have in common is ...... If the goal is not clear enough, they will first take every small step on the road to success ......".
[Try to write your own xUnit]
Kent Beck recommends that you write the xUnit of the language or platform whenever you come into contact with a new language or development platform, in fact, almost all commonly used languages and platforms already have their own xUnit, which is quite the same, but why did the Masters give such suggestions. In fact, Kent Beck means that you can quickly understand the features of the language or platform in this way, and xUnit is actually very simple, as long as you know the principle, you can write it out quickly. This is good news for people who like to write their own underlying code, or those who like control.
[Good at using Ctrl-C/Ctrl-V to write TestCase]
You don't have to worry about code redundancy in TestCase to make it redundant.
[Always function First. improvement can be done later]
The title above can also be changed to another sentence: avoid a transitional design!
[Obsolete use cases]
Cannot bear the child from the wolf. Don't be a pity for old use cases, because they may already be wrong in terms of concept, or only produce wrong results, or lose meaning after a reconstruction. Of course, you don't have to delete them either. Removing (JUnit) from TestSuite or adding the Ignored (NUnit) tag is also a good solution.
[Use TestCase for testing]
If you are not familiar with or have no idea about a certain field before you start to develop a feature or product, or you have no idea about your own capabilities in this field, you will definitely choose to experiment, if you have a unit test tool, we recommend that you use TestCase for testing. It looks like you are writing a verification function to verify whether the implementation of TestCase, and in fact it is the same, what you verify is not the Code itself, but the environment on which the Code depends.
[TestCase should be as independent as possible]
It makes sense to run a TestCase separately.
[Not only code that must pass the test, but also code that must not pass the test]
This is a little trick, and it is also different from the design idea. Such input may cause an exception to be thrown, or a return value indicating "illegal parameters", such as out-of-bounds values, garbled values, or variables of different types, you should test both cases. Of course, we cannot enumerate all incorrect input or external environments, just as we cannot enumerate all correct input and external environments, as long as TestCase can explain the problem.
[The first step in code writing is to use Ctrl-C in TestCase]
This is an advanced technique. Well, yes, I mean, I am not saying that this technique is hard to master, but that it is only when you are already a TDD master, you can appreciate its charm. People who have used TDD many times have this experience. Since my TestCase has already been well written, it can be explained very well. Why can't I copy something from TestCase. Of course, this requires that your TestCase already have a good expression ability. For example, the assertion f (5) = 125 is obviously not assertion f (5) = 5 ^ (5-2) express more content.
[The test case package should be designed to run automatically as much as possible]
If the product needs to deliver the source code, we should allow users to modify or expand the code and run the entire test case package in their own environment. Since the product can run automatically under normal circumstances, why does the test case package not run automatically as a product delivered to the user? Even if the product does not need to deliver source code, the test case package should be designed to run automatically, which greatly facilitates the development of the testing department or the next version.
[Only one red light is on]
Master's suggestion, as mentioned above, is just a suggestion.
[Describe your bugs with TestCase]
If your colleague in another department uses your code and finds a bug, guess what he will do? He will immediately go to your workstation and say, "You have a bug !" ? If he dares to treat you like this, I'm sorry. You must calm down and don't scold him in person. On the contrary, you can smile a little and then calmly say to him: "Oh, yes? Well, give me a TestCase to prove it ." Now the situation is on your side. If he is not ready to answer your fatal blow, I guess he will feel very ashamed and blame himself for being arrogant. In fact, if his TestCase does not require your code too much (but according to your previous contract) and the red light is on, you can determine that it is your bug. Otherwise, the other party is unreasonable. Another advantage of using TestCase to describe a bug is that it will not be exposed again due to future modifications. It has become the content you must check before releasing each version.
The objective of unit testing is
Keep the bar green to keep the code clean
In fact, we only do two things: Make the code work (Keep the bar green) and make the code clean (Keep the code clean). the former is to do things right, the latter is to do a good job. The two are both the two hats in TDD and the causal relationship in the xUnit architecture.
As a category of software testing, unit testing is not created by the xUnit architecture, but has been available for a long time. However, the xUnit architecture makes Unit Testing Direct, simple, efficient, and standardized. This is also one of the reasons why unit testing has developed rapidly in recent years as a major indicator for measuring a development tool and environment. As Martin Fowler said: "In the history of software engineering, there have never been so many huge gains in such simple code !" In addition, the xUnit architecture of most languages and platforms is similar, some of which are only different languages, the most representative of which is JUnit and NUnit. The latter is the innovation and expansion of the former. A unit test framework xUnit should be: 1) enable each TestCase to run independently; 2) enable each TestCase to detect and report errors independently; 3) Enable TestCase to be easily selected before each run. The following are the xUnit Framework concepts I have mentioned. These concepts constitute the core of unit testing theories and tools in the industry:
[Test method/TestMethod]
The minimum unit of the test, expressed as code.
[Test Case/TestCase]
It consists of multiple test methods and is a complete object. It is the smallest unit executed by many TestRunner.
[Test container/TestSuite]
Composed of multiple test cases, it is intended to manually arrange the test cases with the same meaning, and TestSuite can be structured in a tree to facilitate management. In implementation, the form of TestSuite is often also a TestCase or TestFixture.
[Assertion/Assertion]
There are generally three types of assertions: Comparison assertions (such as assertEquals), condition assertions (such as isTrue), and asserted tools (such as fail ).
[Test Device/TestFixture]
Arranges a SetUp method and a TearDown method for each test case. The former is used to initialize certain content before executing the test case or each test method in the test case, the latter is called after executing the test case or every method in the case. It is usually used to eliminate the modifications made to the system by the test.
[Expect Exception/Expected Exception]
It is expected that the test method throws a specified exception as an "assertion" and prevents unexpected termination of the test process due to a reasonable exception.
[Category/Category]
Classification of test cases. In actual use, TestSuite does not use Category any more, and TestSuite is no longer used if Category is used.
[Ignore/Ignored]
Set whether the test case or method is ignored, that is, not executed. Some discarded TestCase cannot be deleted. It can be set to Ignored.
[Testing executor/TestRunner]
The tool used to perform the test indicates the method in which the test is executed. Do not misunderstand it. This is not defined in the Code and is completely unrelated to the test content. For example, text, AWT, swing, or Eclipse view.
The following Sample shows how TDDer compiles a method to generate a Fibonacci series.
(1) first write a TC, asserted fib (1) = 1; fib (2) = 1; this indicates that the first element and the second element of the series are both 1.
Public void testFab (){
AssertEquals (1, fib (1 ));
AssertEquals (1, fib (2 ));
}
(2) The above Code cannot be compiled, Great! -- Yes, I mean Great! Of course, if you are using Eclipse, you do not need to compile it. Eclipse will tell you that there is no fib method. Click mark and ask if you want to create a new fib method. Oh, of course! In order to allow the above TC to pass, we write as follows:
Public int fib (int n ){
Return 1;
}
(3) Now that TC is on the green light, wow! We should celebrate it. Next we need to increase the difficulty of TC, and test the third element.
Public void testFab (){
AssertEquals (1, fib (1 ));
AssertEquals (1, fib (2 ));
AssertEquals (2, fib (3 ));
}
But it is not very nice to write like this. It is better to write like this:
Public void testFab (){
AssertEquals (1, fib (1 ));
AssertEquals (1, fib (2 ));
AssertEquals (fib (1) + fib (2), fib (3 ));
}
(4) New assertions lead to a red light. to reverse this situation, we modify the fib method. Some of the Code is from the above Code Ctrl-C/Ctrl-V:
Public int fib (int n ){
If (n = 3) return fib (1) + fib (2 );
Return 1;
}
(5) Oh, this is really a code written by a bitch! Yes, no? Because TC is the blueprint of the product, as long as the product meets the requirements of TC, OK. So it is not the fault of the fib method, but the error of TC. Therefore, TC needs to further request:
Public void testFab (){
AssertEquals (1, fib (1 ));
AssertEquals (1, fib (2 ));
AssertEquals (fib (1) + fib (2), fib (3 ));
AssertEquals (fib (2) + fib (3), fib (4 ));
}
(6) There are policies and countermeasures.
Public int fib (int n ){
If (n = 3) return fib (1) + fib (2 );
If (n = 4) return fib (2) + fib (3 );
Return 1;
}
(7) Okay, no more. Now it's not a cheap problem. Now the problem is that the Code is redundant, so what we need to do is -- refactoring:
Public int fib (int n ){
If (n = 1 | n = 2) return 1;
Else return fib (n-1) + fib (n-2 );
}
(8) OK. Have you finished writing the fib method? Wrong. A dangerous mistake. You forgot the wrong input. We make 0 to indicate that this item is not found in Fibonacci.
Public void testFab (){
AssertEquals (1, fib (1 ));
AssertEquals (1, fib (2 ));
AssertEquals (fib (1) + fib (2), fib (3 ));
AssertEquals (fib (2) + fib (3), fib (4 ));
AssertEquals (0, fib (0 ));
AssertEquals (0, fib (-1 ));
}
Then change the method fib to make the bar grean:
Public int fib (int n ){
If (n <= 0) return 0;
If (n = 1 | n = 2) return 1;
Else return fib (n-1) + fib (n-2 );
}
(9) The last thing before work is to refactored TC:
Public void testFab (){
Int cases [] [] = {
{0, 0}, {-1, 0}, // the wrong parameters
{1, 1}, {2, 1}; // the first 2 elements
For (int I = 0; I <cases. length; I ++)
AssertEquals (cases [1], fib (cases [0]);
// The rest elements
For (int I = 3; I <20; I ++)
AssertEquals (fib (I-1) + fib (I-2), fib (I ));
}