There are two main ways to change the system. I like to separate them
For editing and praying (edit and pray) and overwriting and modifying (cover and
Modify ). Unfortunately, the previous method is almost a standard practice in the industry. When using this method to make changes, first carefully plan the changes you want and make sure that you understand the changes you want to make.
OfCodeAnd then begin to change. After the modification, run the modified system to check whether the modification has taken effect, and then fix the system as a whole to ensure that the modification has not damaged anything. This last
Steps are indispensable and important. When making the changes, you want to pray that you can do the right thing. After completing the changes, you need additional time to verify if you have done the right thing.
On the surface, "edit and pray" seems to mean
"Be careful", this is a professional job. You need to be careful at the beginning, and when changes become very invasive, you need to be extra careful, because errors are more likely to happen at this time.
. However, it is a pity that security does not depend on your degree of care. I don't think anyone will allow a surgeon to cut you with a buttered knife just because he will perform the procedure very carefully. Superb software changes
Like superb surgical procedures, there must be profound Technology in addition to carefulness. Without proper tools and technologies, even "Be careful" won't play much role.
"Overwrite and modify" is another method. The concept behind it lies in
Yes. Open a security net when we change the software. Here the so-called "safety net" doesn't mean to put it under the table. When we fall down from the Chair, it can hold us like a cloak.
"Build" on the code we modified to ensure that bad changes do not leak out and infect other parts of the software. Overwriting software means overwriting it with testing. When there is a good set of tests on a piece of code
We can safely modify it and quickly verify that the modification is good or bad. We will continue with the same care, but with the test feedback, we will be able to make more detailed changes.
If you are not familiar with this test method, let's talk about the above.
It may be a bit confusing. Traditionally, testing is always written and executed after development. GroupProgramThe tester writes the code, and the other tester tests the code to see if it meets
Specific requirements. Some very traditional development teams are developing software in this way. Such teams can also get feedback, but the entire feedback cycle is very long, often after a few weeks or even months, another small
The Group will tell you if you have done the right thing.
Testing in this way can actually be expressed as "testing to verify correctness ". Although this is a good goal, tests can also be used to do other things. We can "detect changes through tests ".
In traditional terms, this is called regression testing ). We run tests cyclically to test known good behaviors so that the software can still work as before.
When the areas to be changed are surrounded by tests, these tests act like a software clamp (vise )". You can use this "software clamp" to fix most of the behavior of the target software, only to change the places you really want to change.
Software clamp
Clamp: a clamp tool made of metal or wood. It is usually composed of two parts that are opened and closed by a screw or lever for positioning in the wood industry or hardware industry. [Traditional American English dictionary, version 4th]
A test that can detect changes is like a clamp on our code to fix the code behavior. So when we make changes, we can know that we only change one behavior at a specific time. In short, we are able to take control of our work.
Regression testing is a good idea. So why do people do not enter more frequently?
What about row regression testing? This is because regression testing has a small problem: During regression testing
Interface. Regression testing is traditionally regarded as an application-level test, regardless of whether the target application is a Web application, a command line program, or a GUI program. However, this is only one
An unfortunate traditional concept. In fact, the feedback we get from regression testing is very useful. Therefore, it is of great benefit to apply regression testing to a more fine-grained level.
To prove this, let's make an ideological experiment: Let's assume that we are
One-step tracking of a large function that contains a large number of complex logic. We analyze and think, and communicate with people who are more familiar with the Code. Then we proceed to make changes. We want to ensure that the changes are not broken
What, but how can we ensure this? Fortunately, we have a quality group that has a set of regression tests that can run at night. So we called them and asked them to schedule a test.
They agreed to schedule a regression test for us at night. Fortunately, this call is early, because other groups usually ask them to schedule a regression test around Wednesday.
They may not be able to allocate time periods and machines. After hearing this, we can rest assured that we will continue to work. There are five more changes to be made just now, and they are all in the complex
. At the same time, we also know that there are several other people who are as busy as we are to modify the code and wait to schedule a test.
We received a call the next morning. Daiva of Test Department
We are told that tests on ae1021 and ae1029 failed last night. She couldn't be sure it was because of our changes, but she called us because she knew we would help her with this.
Things. We will conduct debugging to check whether the failure is caused by some changes or other reasons.
Does the above scenario sound true? Unfortunately, it is very real.
Let's look at another scenario.
We need to modify a very long and complex function. Fortunately, we found a group of unit tests for it. The person who finally came into contact with the code wrote a group of about 20 unit tests, thoroughly testing the code. We ran this set of tests and found that all tests passed. Next, let's look at these tests to understand the actual behavior of the Code.
We are ready to make changes, but it is difficult to come up with specific changes.
Dynamic Method. The code is ambiguous. We hope to have a better understanding of the code before getting started. Existing tests do not cover all situations, so we want to make the code clearer so that we can
More confidence in the changes made. In addition, we don't want ourselves or anyone else to go through such a painful process again, which is a waste of time!
We started to refactor the Code a little bit. We extract some methods
And move some conditional logic. After every small change, we will run the unit test mentioned above. It is passed almost every time we run it. We committed the attack a few minutes ago.
An error reversed a conditional logic, but the unit test quickly gave a failed result, so we were able to correct the error in about one minute. After reconstruction, the Code becomes clearer.
. We have made the changes and are sure that our changes are correct. Next, we added several tests to verify the newly added features. As a result, the next programmer facing the code will do it
It's much easier, and he has a full set of tests covering all of its features.
Do you want to get feedback in one minute? Or do you want to wait for the whole night? Which of the above scenarios is more efficient?
Unit testing is one of the most important components used to deal with legacy code. Regression tests at the system level are indeed great, but in contrast, small and local tests are invaluable, and they can continuously provide you with feedback during the change process, this greatly enhances the security of refactoring.
2.1 What is Unit Testing
Unit
Testing) has a long history in the software development field. In most unit tests, there is a common idea that they are composed of a group of independent tests, each of which is
Independent software components. So what are components? In fact, components have multiple definitions, but in the unit test field, we usually care about the most "Atomic" behavior unit of a system. For example, in the process
In the code of program design, "unit" generally refers to a function, while in the object-oriented code, it refers to a class.
Test Tool
Ben
I used the term test tool (Test
Harness ). This term stands for the testing code we write to test a part of the software and the code used to run the testing code. We can use a variety of different tests for the code
. Chapter 2 discusses the xunit test framework and fit framework. Both of them can be used for the test work described in this book.
So can we test only one function or
Class? In a procedural system, it is usually difficult to test functions in isolation, because in this system, the top-level function calls other functions, and the latter calls other functions, until the machine layer. While in the face
In an object system, it is easier to test classes separately. In fact, classes are often not "isolated" creatures. Think about it. How many of the classes you have written are not using other classes? Very few
These very few elements are often small data classes or data structures, such as stacks and queues (even such classes may be used in other classes ).
Isolation of testing is an important aspect of unit testing. Why is it important? After all, many errors may occur when integrating all parts of the software. Shouldn't it be more important for large tests that can cover a wide range of functional areas in the code? It is true that they are important and I do not deny this. However, there are some problems in large tests:
Q
Location: the farther the test is from the tested user, the more difficult it is to determine what the test failure actually means. It takes a lot of work to precisely locate the root cause of test failure. You have to check the test input and test failure.
And then you have to determine which point of failure occurs on the execution path from input to output. Although this kind of work is inevitable for unit testing, the workload is usually very small.
Q execution time: Large tests often take longer to run. However, such long-term consumption is often intolerable. Tests that take too long to run often fail to run.
Q coverage: in large tests, it is often difficult to see the relationship between a piece of code and the value used to test it. We can usually use overwriting tools to find out if a code segment is overwritten by a test, but when a new code is added, it may take a considerable amount of work to create a high-level test to test the new code.
Large
The most unacceptable thing about type testing is that you can locate errors by running tests frequently. However, this is difficult to implement. Suppose we run the test and pass the test, and then we make some changes.
The test fails, so we can precisely know where the problem was triggered, that is, somewhere in the last change we made. So we can roll back the changes and try again. This
A scenario may seem okay. However, if we test a lot, the execution time may be unbearable. we can imagine that what we do more is to avoid running it as much as possible, so you cannot
The error locating is achieved.
Unit tests do what large-scale tests cannot do.
Unit tests can be used to test a piece of code independently. We can group tests to run certain tests under certain conditions and run other tests under other conditions. We can also
Speed locating error. If we think there is an error in a piece of code and we can use this code in the test tool, we can quickly compile a test, let's see if we guess the error is no.
It's really there.
Below are the quality of good unit testing:
(1) fast operation;
(2) help us locate the problem.
In the industry, people are judging whether a specific test is a unit test.
Problems often change. If another product class is involved in a test, is it still a unit test? To answer this question, let's go back to the two quality points mentioned above, that is, the test run.
Not fast? Can it help us quickly locate errors? For example, some tests are large and many classes are used. In fact, this test may look like a small integration test. As far as they are concerned
But what if you run them together? If a test not only tests a class but also tests several classes that work with the class, it will often "grow bigger ". If you are not spending any time
So that a class can be individually instantiated in the test tool, can you still expect it to become easier when more code is added to the system? Never. People will keep pushing and
Over time, it may take a tenth of a second to complete a short test.
A unit test that takes one to ten seconds to complete is a slow unit test.
I am serious about this. When I write a book
Unit Testing is just like a century. If you don't believe it, let's do some simple arithmetic: Suppose you have a project, which contains 3
There are classes. Each class has about 10 tests on average. The total number is 30.
000 tests. If all these tests can be completed in seconds, how long will it take for the entire project to be tested? Nearly an hour! For feedback, the waiting time is not short. What
? Your project does not have 3
000 classes? There is always half of it. It still takes about half an hour. On the other hand, what if our test takes 1% seconds? Obviously, we have changed from waiting for an hour
Wait for 5 to 10 minutes! In this way, although I am more cautious about using only part of the unit tests, even if I run them all once every few hours, I am no longer afraid.
In addition, according to Moore's Law, in my lifetime I hope to see that even testing feedback on the largest system can be completed in almost a moment. I guess at that time, working in such systems would be like working in a bunch of code with a single bite. Once you make a mistake, the system will immediately "Bite" to let you know.
Unit tests run fast. Not a unit test.
Some tests are easy to confuse with documentary meta tests. For example, the following tests are not unit tests:
(1) Interacting with the database;
(2) Communication between networks;
(3) the file system is called;
(4) You need to make specific preparations for the environment (such as editing the configuration file) to run.
Of course, this does not mean that these tests are bad. Writing them is often valuable, And you usually write them in unit test tools. However, it is necessary to distinguish them from true unit tests, because you can know which tests you can run quickly (when you modify your code.