Rebuilding notes-building a testing system and restructuring a building system
This article is in the study of the summary, welcome to reprint but please note the Source: http://blog.csdn.net/pistolove/article/details/42167015
As a programmer, I do not know whether you are developing very few or even not writing test programs. Most people may think this is normal. In fact, it is quite normal from a personal perspective, after all, there are testers dedicated to testing! However, if you can carefully observe where Programmers spend the most time, you will find that writing code is actually a very small part. Some time is used to decide what to do next, some time is spent on design, and the most time is used for debugging. I am sure every reader still remembers the countless hours of debugging and the countless times of staying up all night long. Every programmer can tell a story that takes a day (or even more) only to identify a small problem. Fixing errors is usually fast, but identifying errors is a nightmare. When you fix an error, there will always be another error, and it will take a long time to notice it. Then you have to spend time searching for it. It seems that we really need to build a testing system.
(I) value of self-testing code
In fact, if you carefully observe where Programmers spend the most time, you will find that writing code is actually only a very small part. Some time is used to decide what to do next, some time is spent on design, and the most time is used for debugging. I am sure every reader still remembers the countless hours of debugging and the countless times of staying up all night long. Every programmer can tell a story that takes a day (or even more) only to identify a small problem. Fixing errors is usually fast, but identifying errors is a nightmare. When you fix an error, there will always be another error, and it will take a long time to notice it. Then you have to spend time searching for it.
Therefore, we should look at the problem in the long run, rather than causing inconvenience in the future for temporary convenience. Just as we often say to ourselves, "Well, it looks good. It looks like we can already work normally. Then we can continue to develop other content while still developing the code ". Can it work properly? Have you seen it work properly? Have you tested it? We often deceive ourselves, and may have to deceive ourselves, because developers have so much time! However, we should build a testing system to find out the problems. However, writing excellent test program code can greatly improve my programming speed, even if I don't refactor it.
I think each class should have a test function and use it to test its own class. In general, we will perform incremental development, so it is best to add tests for each class at the end of each increment to ensure correctness. For example, when a project is relatively small, it increments every week, and the tests executed are quite simple. Even so, it is very troublesome to do these tests, because each test outputs the results to the console, you must check them one by one. Later, I realized that there was no need to stare at the screen to check whether the information obtained from the test was correct, so that the computer could help me. This ensures that all tests are fully automated and allow them to check their own test results.
A set of tests is a powerful Bug detector that can greatly reduce the time required to search for bugs. Of course, it is not that easy to persuade others to do the same. Writing a test program means writing a lot of additional code. Unless you experience the improvement in programming speed of this method, self-testing will not show its meaning. Many people have never learned how to write a test program, or even have never considered a test. This is not good for writing self-test code. If you need to manually run the test, it is even more depressing that no one is willing to keep an eye on the screen; but if you can run it automatically, writing the test code is really interesting.
Generally, we think that the test code should be written after the program is developed. In fact, the most useful time to write test code is before you start programming. Whenever you need to add features, write the corresponding test code first. This sounds like a defect, but it is not actually true. Because writing the test code is actually asking yourself: add this function to what to do. Writing Test code also enables you to focus on interfaces rather than implementation, which is always a good thing. The pre-written test code also adds a clear end sign to your work: Once the test code runs properly, the work will end.
The truth is always true. Although I believe everyone can benefit from self-testing, this is not the focus of this article. This article focuses on refactoring, which requires testing. If you want to refactor, you must write the test code. In JAVA, the common test method is testing main, which means that each class should have a main () for testing (). This is a reasonable habit, but it may not be easy to manipulate. In fact, it is difficult to run multiple tests easily. A good practice is to create an independent class for testing and run it in a framework to make the testing work easier.
(Ii) JUnit testing frameworkThis article uses the JUnit testing framework, which is very simple, but allows you to perform all the important things required for testing. The following describes how to develop and test code for some I/O classes.
Note: (a) TestCase inherits the Asset class and implements the Test interface. (B) TestSuite implements the Test interface.
(1) first create a FileReaderTester class to test the file reader. Any class that contains the test code (that is, the test case) must inherit the TestCase class provided by the test framework. Test is a Test suite, which can contain Test cases and other Test suites.
public class FileReaderTester extends TestCase {public FileReaderTester(String method) {super(method);}}
(2) the newly created class requires a constructor. After that, we can add the test code. The first choice is to set the test fixture, that is, the test object sample. To read a file, prepare the following test file:
Bradman 99.95 50 82 10 6789 334 29Pollock 60.95 22 60 4 2256 334 4Marry 80.95 56 88 4 2256 334 29Jone 77.65 70 67 4 6789 334* 11Sutcliffe 63.25 56 80 8 6789 331934 18
(3) Before using this document, we need to prepare the test fixture. TestCase provides two functions: setUp () is used to generate related objects, and tearDown () is used to delete them. Now we have a proper test fixture, so we can start writing the test code. First test read (), read some characters, and then check whether the subsequent read characters are correct.
public void testRead throws Ioexception{ char ch = '&'; for(int i = 0; i < 4; i++){ ch = (char)_input.read(); assert('d' == ch)}}
Assert () assumes the automatic test role. If the value of assert () is true, everything is fine. Otherwise, we will receive an error notification. The following describes how to run the test process:
(1) The first step is to generate a test suite. Design a suite () as follows:
public static Test suite() {TestSuite suite = new TestSuite();suite.addTest(new FileReaderTester("testRead"));suite.addTest(new FileReaderTester("testReadAndEnd"));return suite;}
This test suite contains only one test case object, namely the FileReaderTester instance. When creating a test case object, pass the name of the function to be tested as a string to the constructor to create an object for testing the specified function, this test uses the JAVA reflection mechanism and object Association.
(2) An independent TestRunner class is also required. TestRunner has two versions. Here we select the "text interface" version. For each running test, JUnit outputs a good user interface, so that you can intuitively see the test progress. It will tell you how long the test took. If there are no errors in all tests, it will say OK and tell you how many tests have been run.
public static void main(String[] args) {junit.textui.TestRunner.run(suite());}
This Code creates a TestRunner and requires it to run the FileReaderTester class. However, when executing the command, we will see:
Run the test frequently. Take the test into consideration each compilation. Each test is executed at least once a day. During refactoring, you can run only a few tests, which are mainly used to check the code being developed or organized. Yes, you can run a few tests. This is certainly faster. Otherwise, the whole test will reduce your development speed and you may hesitate whether to proceed like this. What happens if a test error occurs? To demonstrate that I intentionally introduced bugs so that the test file could not be found. You will see:
In this way, we can quickly find out where the error is and modify the code. When writing the test code, we should first let it fail. The reason for doing so is to prove that the test mechanism can indeed run, and the test does test what it should test.
The detailed code run above is provided below, so that you can better understand the test code:
Import java. io. fileReader; import java. io. IOException; import junit. framework. test; import junit. framework. testCase; import junit. framework. testSuite; public class FileReaderTester extends TestCase {private FileReader _ input = null; public FileReaderTester (String method) {super (method);} public static void main (String [] args) {junit. textui. testRunner. run (suite () ;}@ Overrideprotected void setUp () throws Exception {try {_ input = new FileReader ("data.txt");} catch (Exception e) {throw new RuntimeException ("test file cannot be opened") ;}@ Overrideprotected void tearDown () throws Exception {try {_ input. close () ;}catch (Exception e) {throw new RuntimeException ("close test file error") ;}} public void testRead () throws IOException {char ch = '&'; for (int I = 0; I <4; I ++) ch = (char) _ input. read (); assertEquals ('D', ch);} public static Test suite () {TestSuite suite = new TestSuite (); suite. addTest (new FileReaderTester ("testRead"); return suite;} public void testReadAndEnd () throws IOException {int ch = 1234; for (int I = 0; I <141; I ++) ch = _ input. read (); assertEquals (-1, ch); // compare whether equal }}
(Iii) unit test and function test
The JUnit framework is used for unit testing. Unit testing aims to improve the productivity of programmers. Unit Tests are highly localized and each test class belongs to a single package. It can test interfaces of other packages. In addition, it assumes that all other packages are normal.
Functional Tests are completely different. They are used to ensure the normal operation of the software. They guarantee quality from the customer's perspective and do not care about the productivity of programmers. They should be developed by an independent team that prefers to search for bugs. This team should use heavyweight tools and technologies to help themselves develop good functional tests.
In general, functional testing tries its best to treat the entire system as a black box. In the face of a tested system with a GUI, they operate that system through the GUI. In the face of file update programs or database update programs, the function test only observes the data changes caused by specific input.
Once a functional tester or end user finds a bug in the software, at least two things are required to remove it. Of course, you must modify the code to eliminate errors, but you should also add a unit test to expose this bug. In fact, when we receive a bug report, we should first write a unit test to make the bug emerge.
(Iv) Add more tests
Now we should add more tests. The principle we follow is to observe all the things the class should do, and then test any possible failure situation of any function. Remember, testing should be a risk-driven behavior, with the purpose of identifying the current or future possible errors. So I won't test the access functions that only read or write a field, because they are too simple to make mistakes. This is also very important. Writing too many tests often results in less than enough tests. Test where you are most worried about errors, so that you can get the best benefit from the test work.
(1) Focus the test firepower on the boundary that may cause errors.
An Important Skill in testing is "Finding boundary conditions ". For read (), the boundary condition should be the first character, the last character, and the second to the last character:
public void testReadBoundaries() throws IOException {assertEquals("read first char", 'B', _input.read());int ch;for (int i = 0; i < 222; i++)ch = _input.read();assertEquals("read last char", 8, _input.read());assertEquals("read first char", -1, _input.read());}
You can add a message to the assertion. If the test fails, the message is displayed.
Searching for boundary conditions also involves looking for special situations that may cause test failure. For file-related tests, empty files are a good boundary condition:
public void testEmptyRead() throws IOException {File empty = new File("empty.txt");FileOutputStream out = new FileOutputStream(empty);out.close();FileReader in = new FileReader(empty);assertEquals(-1,in.read());}
(2) When an error occurs, do not forget to check whether the expected exception is thrown.
During the test, do not forget to check the expected errors as scheduled. If you try to read the stream after closing it, you should get an IOException, which should also be tested:
public void testReadAfterClose() throws IOException {_input.close();try {_input.read();fail("no exception for read past end");} catch (IOException e) {}}
Follow these rules to enrich your tests. For some complex classes, it may take some time to browse their interfaces. In this process, you can really understand this interface. As the number of test classes increases, you can generate another class to include a test suite composed of other test classes.
class MasterTester extends TestCase{public static void main(String[] args) {junit.textui.TestRunner.run(suite());}public static Test suite(){TestSuite suite = new TestSuite();suite.addTest(new TestSuite(FileReaderTester.class));suite.addTest(new TestSuite(FileWriterTester.class));//......return suite;}}
(3) do not write the test because the test cannot capture all bugs, because the test can indeed capture most bugs.
Object Technology has a "subtle": inheritance and polymorphism make testing more difficult, as there will be many combinations to be tested. If you have three cooperative abstract classes and each abstract class has three subclasses, you have a total of nine available classes and 27 combinations. Instead of trying to test all possible combinations, we don't have that much time, but we should try to test as many classes as possible, which can greatly reduce the risks caused by various combinations. We may always miss something, but I think it is better to spend some reasonable time to catch most bugs than to "catch all bugs in a lifetime ".
This article mainly introduces the value of self-testing code and uses the JUnit testing framework for simple testing. The purpose is to demonstrate that building automatic testing is very important for developers. In fact, what we lack is not the ability to solve the problem, but the ability to find out where the problem actually exists. Instead of spending so much time looking for a bug, it is better to stop it in the development process. The content introduced in this article is still biased towards the theoretical aspect, but I have to introduce it, Because refactoring requires a reliable test environment, even if you do not recompile the test program.
PS: the next article will officially start refactoring: Restructuring notes-refining functions. I hope this article will help you.