|
Level: elementary About author aiang technology Shanghai Company July 01, 2002
With the extensive promotion of refactoring technology and XP software engineering technology, the role of Unit Testing becomes more and more important in software engineering, A concise, easy-to-learn, widely applied, efficient, and stable unit test framework plays a vital role in the successful implementation of unit tests. In the Java programming statement environment, JUnit framework is a JavaProgramThis is an excellent testing framework adopted and tested by developers, but most programmers who have not tried JUnit framework are learning how to compile unit tests for their own development projects, I still think it is difficult. This may be because JUnit is working with the Framework.CodeThe User Guide and documentation attached with utilities focus on interpreting the design methodology of the unit test framework and simple class instructions for use, while on the specific testing frameworks (JUnit) there is no detailed explanation of how to implement unit test and how to update and maintain the existing unit test code during project development. Therefore, this document further supplements and describes the documents attached to JUnit, so that JUnit can be used by more development teams, this allows unit testing, refactoring, and XP technologies to be better promoted in more development teams.
1. unit test WRITING PRINCIPLES The unit tests listed in the attached document of JUnit are confusing, because almost all sample units are methods for an object, it seems that the unit test of JUnit is only applicable to the static constraints of the class organizational structure, so that beginners doubt the effect of unit test under JUnit. Therefore, we need to redefine how to determine valuable unit tests and how to write and maintain these unit tests, this allows more programmers to accept and familiarize themselves with the compilation of unit tests under JUnit. In the design of the JUnit unit test framework, the author sets three overall objectives. The first is to simplify the compilation of the test, which includes the study of the Test Framework and the compilation of the actual test unit; the second is to make the test unit persistent; the third is to use existing tests to compile related tests. From these three objectives, we can see that the basic design of the unit test framework is still based on our existing testing methods and methods, it only makes the test easier to implement, expand, and maintain durability. Therefore, the Unit Test principles can be used for reference and use from our usual testing methods.
2. How to Determine Unit Tests In our usual tests, a unit test is generally targeted at a specific feature of a specific object. For example, suppose we have compiled a class package implementation for a connection pool that accesses a specific database, we will establish the following unit tests:
- After the connection pool is started, whether or not a number of database connections are established in the pool according to the defined rules
- Apply for a database connection, whether to directly obtain reference of cache connections from the pool according to the defined rules, or establish a new connection
- After a database connection is released, whether the connection is released by the pool or cached according to the defined rules for future use
- Whether the backend housekeeping thread releases an expired connection application according to the defined rules
- If the connection has a time limit, whether the backend housekeeping thread regularly releases expired cache connections
Only some possible tests are listed here, but we can see from this list the granularity of unit tests. A unit test is based on the explicit characteristics of an object. The unit test process should be limited to a specific thread range. According to the above, the testing process of a unit test is very similar to the definition of a use case, but the unit test granularity is generally smaller than the definition of use case, which is easy to understand, because use case is based on individual transaction units, while unit test is based on the specific characteristics of a group of aggregated objects, generally, a transaction uses many system features to fulfill specific software requirements. From the above analysis, we can conclude that the test unit should convert the internal state of an object into the basic writing unit. A software system is the same as a well-designed automobile. The state of the system is determined by the state of each discrete component in the system at the same time, therefore, to determine that the final behavior of a system meets our initial requirements, we must first ensure that the status of each part of the system meets our design requirements, therefore, the focus of our testing unit should be on determining the object state transformation. However, note that not all object group features need to be written into independent test units. The principle of how to filter valuable test units in object group features lies in junittest infected: programmers love writing tests has the correct description in the article. You should introduce test units where possible errors are introduced. Generally, these places exist in a specific boundary condition and are complex.AlgorithmAnd the code logic that requires frequent changes. In addition to these features, they need to be compiled into independent test units. In addition, some object methods with Complicated Boundary Conditions should also be compiled into independent test units, this part of unit testing has been well described and explained in the JUnit document. After basically determining the unit tests to be written, we should also ask ourselves: Can we tell ourselves with confidence if the code passes these unit tests, we can determine that the program runs correctly and meets the requirements. If we cannot be very sure, we should check whether there are any missing unit tests to be written or review our understanding of software requirements. Generally speaking, when you start to use unit tests, more unit tests are always correct. Once we determine the test unit to be written, we should
3. How to Write Unit Tests In XP, it is emphasized that unit testing must be written by the class package compiler. This limitation is required for the test goal we set. Only in this way can the test ensure that the running temporal behavior of the object meets the requirements. However, through the test of class interfaces, we can only ensure that the object complies with static constraints, therefore, it is required that we open a certain internal data structure during the testing process, or establish appropriate data records for specific running behaviors, and expose the data to specific test units. This means that we must modify the corresponding class package when writing unit tests. Such modifications also happen in the test method we used previously, therefore, the previous test marks and other test skills can still be improved in JUnit testing. Since the overall objective of unit testing is to ensure that our software is correct during operation, when we write a unit test for an object, we must not only ensure that the static constraints of the class comply with our design intent, but also ensure that the running status of the object under specific conditions conforms to our preset. The database buffer pool example shows that a buffer pool exposes a group of APIs to other objects, these include parameter settings for the pool, initialization of the pool, destruction of the pool, obtaining a data connection from the pool, and releasing the connection to the pool, for other objects, it is not necessary to know the changes in the internal state of the pool due to the triggering of various conditions. This is also in line with the encapsulation principle. However, the status of the pool object changes. For example, the number of cached connections increases under certain conditions, and a connection needs to be completely released after a long enough operation to update the pool connection, although the external object does not need to be clear, it ensures that the program runs correctly. Therefore, our unit test must ensure that these internal logics are correctly run. Testing and debugging of the compilation language is difficult to track the running logic process, but we know that no matter how the logic runs, if the state conversion conforms to our behavior settings, the verification results are obviously correct. Therefore, when performing a unit test on an object, we need to analyze and compare the majority of state transitions to verify the behavior of the object. The State is described by a series of State data. Therefore, write a unit test to first analyze the state change process (the state transition diagram clearly describes this process ), then, the status data of the analysis is determined based on the definition of the status, and finally the access to the internal status data is provided. In the database connection pool example, after analyzing the status change of the defaultconnectionproxy object implemented by the pool, we decided to publish the oracleconnectioncacheimpl object that represents the status to the test class. See example 1.
Example 1/*** this class simply wraps Oracle's implementation of the Data Connection buffer pool. **/Public class defaultonproxy extends connectionproxy {Private Static final string name = "Default connection proxy "; private Static final string description = "this class simply wraps Oracle's implementation of the Data Connection buffer pool. "; Private Static final string author =" Ion-Global.com "; Private Static final int major_version = 0; Private Static final int minor_version = 9; Private Static final Boolean pooled = true; private connectionbroker = NULL; private properties props; private properties propdescriptions; private object initlock = new object (); // test code begin... /* to be able to understand the state changes of an object, you need to take some private Some variables provide public access interfaces (or access interfaces for the same class package), so that the test unit can effectively judge the object state transition, in this example, an access interface is provided for the encapsulated oracleconnectioncacheimpl object. */Oracleconnectioncacheimpl getconnectioncache () {If (connectionbroker = NULL) {Throw new illegalstateexception ("You need start the server first. ");} return connectionbroker. getconnectioncache ();} // test code end... after the internal status data is published, we can write our test unit. The selection method and scale of unit test have been described in the previous section, however, you still need to note that the assert method will throw an error. You should use the assert method to judge at the end of the test method to ensure that the resources are released. For the database connection pool example, we can establish the defaultconnectionproxytest test class and create several test cases at the same time. Example 2/*** this class performs a simple test on the class in Example 1. **/Public class defaultonproxytest extends testcase {private defaconnecticonnectionproxy conproxy = NULL; private oracleconnectioncacheimpl cacheimpl = NULL; private connection con = NULL;/** set the fixture of the test, establish the necessary test start environment. */Protected void setup () {conproxy = new defaultconnectionproxy (); conproxy. start (); cacheimpl = conproxy. getconnectioncache ();}/** tests the status of the object in Example 1 after the service is started, and checks whether the connection pool parameter settings are correct after the service is started. */Public void testconnectionproxystart () {int minconnections = 0; int maxconnections = 0; assertnotnull (cacheimpl); try {minconnections = integer. parseint (propertymanager. getproperty ("defaultconnectionproxy. minconnections "); maxconnections = integer. parseint (propertymanager. getproperty ("defaultconnectionproxy. maxconnections ");} catch (exception e) {// ignore the exception} assertequals (CA Cheimpl. getminlimit (), minconnections); assertequals (cacheimpl. getmaxlimit (), maxconnections); assertequals (cacheimpl. getcachesize (), minconnections);}/** tests the object in Example 1 to obtain the database connection and check whether a valid database connection can be obtained. After obtaining the connection, whether the status of the connection pool changes according to the established policy. Because the assert method throws an error object, try to place the assert Method to the end of the method for collective testing. In this way, resources opened in the method can be effectively disabled correctly. */Public void testgetconnection () {int cachesize = cacheimpl. getcachesize (); int activesize = cacheimpl. getactivesize (); int cachesizeafter = 0; int activesizeafter = 0; con = conproxy. getconnection (); If (con! = NULL) {activesizeafter = cacheimpl. getactivesize (); cachesizeafter = cacheimpl. getcachesize (); try {con. close () ;} catch (sqlexception e) {}} else {assertnotnull (CON) ;}/ * if the number of actually used connections in the connection pool is less than the number of cached connections, check whether the new data connection is obtained from the cache, and whether the connection pool establishes a new connection */If (cachesize> activesize) {assertequals (activesize + 1, activesizeafter ); assertequals (cachesize, cachesizeafter);} else {assertequals (activesize + 1, cachesiz Eafter) ;}}/** tests the database connection release of the object in the example, and checks whether the connection pool status changes according to the established policy after the connection is released. Because the assert method throws an error object, you can place the assert Method to the final group of the method for testing. In this way, resources opened in the method can be effectively disabled correctly. */Public void testconnectionclose () {int minconnections = cacheimpl. getminlimit (); int cachesize = 0; int activesize = 0; int cachesizeafter = 0; int activesizeafter = 0; con = conproxy. getconnection (); If (con! = NULL) {cachesize = cacheimpl. getcachesize (); activesize = cacheimpl. getactivesize (); try {con. close ();} catch (sqlexception e) {} activesizeafter = cacheimpl. getactivesize (); cachesizeafter = cacheimpl. getcachesize () ;}else {assertnotnull (CON);} assertequals (activesize, activesizeafter + 1);/* if the number of cache connections in the connection pool is greater than the minimum number of cache connections, check whether the number of cached connections is reduced by one after the data connection is released. If the number of cached connections remains at least */If (cachesize> minconnecti ONS) {assertequals (cachesize, cachesizeafter + 1);} else {assertequals (cachesize, minconnections);}/** releases resources when a test starts. */Protected void teardown () {cacheimpl = NULL; conproxy. destroy ();} public defaconnectionproxytest (string name) {super (name);}/** you can simply run this class to test the test units contained in the class. */Public static void main (string ARGs []) {JUnit. textui. testrunner. Run (defaultconnectionproxytest. Class );}}
|
After the unit test is complete, we can use the testsuite object provided by JUnit to organize the test unit. You can determine the test sequence and then run your test.
4. How to maintain Unit Tests Through the above description, we have a basic understanding of how to determine and compile the test, but the demand is always changing, so our unit test will also evolve according to the demand changes. If we decide to modify the behavior rules of the class, we can make it clear that, of course, we will modify the test units for this class to adapt to the changes. However, if the behavior definitions of the class with only a call relationship remain unchanged, the unit test is still reliable and sufficient, at the same time, if the State Definition of the object containing the behavior change class has no direct relationship with it, the test unit still takes effect. This result also demonstrates the advantages of the encapsulation principle.
About the author
|
|
|
Shen Wenbo: born in 1973, is now a senior technical consultant at aiang technology Shanghai. I have long experience in Relational Database Object Modeling and are familiar with the Java language. Currently, I am engaged in OOA, OOD and enterprise applications. You can contact him by email alair_china@yahoo.com.cn. |
|