JUnit takes a horse view

xiaoxiao2021-03-05  27

JUnit takes a horse view

Author: JWT

Outmyth

Translation

[Translator Note] This article is translated from JUnit 3.8.1 released version of JUnit A Cook's Tour.

1 Introduction

In an earlier article (see Test Infected: Program Writing Tests, Java Report, July 1998, Volume 3, Number 7), we describe how to use a simple framework to write repeatable tests. In this article we will rush to the inner details and show you how the framework is constructed.

We carefully study the Juint framework and think about how to construct it. We have discovered many lessons at different levels. In this article, we will try to communicate immediately, which is an desperate task, but at least it is in the context of the design and constructing a value confirmed by you.

We have triggered a discussion about the framework goals. During expression of the frame itself, the target will repeat many small details. Since then, we present the design and implementation of the framework. Design will be described from the perspective of mode (surprise, surprise) and as a beautiful program. We summarize some outstanding ideas about framework development.

2. What is JUnit's goal?

First of all, we have to return to the assumption of development. If the automatic test of a program feature is missing, we assume that it cannot work. This seems to be more secure than mainstream assumptions, and the mainstream assumptions believe that if the developer guarantees a program feature to work, it will always work forever.

From this point of view, when developers write and debug code, their work is not completed, they must also write tests to demonstrate that program can work. However, everyone is too busy. They have to do too much, they don't have sufficient time for testing. I have already had too many code needs to be written. How do I write test code? Answer me, Mr. Tough Project Manager. Therefore, the primary goal is to write a framework, and the developer can see the actual light of the test in this framework. This framework must use a common tool to learn from too many new things. It can't be more work that must be prepared for a new test. Repeatability must be excluded.

If all tests do this, you will be able to write expressions only in a debugger. However, this is not sufficient for testing. Tell me that your program is now able to work, there is no help to me, because it doesn't guarantee that your program will work from every minute I now integrate, and it doesn't guarantee you. The program will still work for five years, then you have left a long time.

Thus, the second goal of the test is to generate a test that sustainable to maintain its value. Other people other than the original author must be able to perform testing and explain its results. You should be able to combine different authors and run together without worrying about mutual conflicts.

Finally, you must be able to generate a new test with existing tests. Generating a device (SETUP) or fixture is expensive, and a frame must be able to reuse the fixture to run different tests. Oh, is there anything else?

3.junit design

The JUnit is designed to be used in Patterns Generate Architectures (see "Patterns Generate Architecture", Kent Beck and Ralph Johnson, Ecoop 94). The idea is to apply a system by using the mode from zero, then one after another, until you get the system architecture. We will put forward the architectural issues that need to be resolved, summarize the mode used to solve the problem, and then demonstrate how to apply the mode to JUnit. 3.1 From this start-TestCase

First we must build an object to express our basic concepts, Testcase. Developers often have test cases in their minds, but they use many different ways when they achieve them.

Print statement

Debugger expression

Test script

If we want to easily manipulate tests, they must build them into objects. This will get a test that is only hidden in the developer's mind and makes it avattern that supports the goal of our creation test, that is, you can continue their value. At the same time, the developer of the object is used to using the object to develop, so the decision to build the test system supports our goals - making the testing more attractive (or at least not gorgeous).

Command mode (see Gamma, E., ET Al. Design Patterns: Elements of Reusable Object-Oriented Software, AdDison-Wesley, Reading, MA, 1995) can better meet our needs. Excerpt, "" Pack a request into an object so that you can use different requests to parameterize the customer; queuing or logging the request for the request ... "Command tells us to generate one Object and give it a "Execute" method. The following code defines the TestCase class:

Public Abstract Class Testcase Implements Test {

...

}

Because we expect to be reused by inheriting, we declare them as "public abstract". Temporarily ignore the fact that its interface TEST is implemented. Given the current designed needs, you can view TestCase as an isolated class.

Every TestCase has a name when it is created, so if a test fails, you can identify which test of failure.

Public Abstract Class Testcase Implements Test {

PRIVATE FINAL STRING FNAME;

Public Testcase (String Name) {

FNAME = Name;

}

Public Abstract void Run ();

...

}

In order to explain the evolution of JUnit, we will use the picture (Diagram) to display the snapshot of the architecture. Our tags we use are simple. It uses a tip box containing the related mode to mark classes. When the role in the mode is obvious, only the name of the mode is displayed. If the role is not clear, add the name of the participant associated with this class in the Clamp. This tag can minimize the challenge and first see Applying Design Patternalns in Java (see Gamma, E., Applying Design Patterns in Java, In Java Gems, Sigs Reference Library, 1997). Figure 1 shows this label for TestCase. Since we are dealing with a separate class and there is no unclear place, only the name of the mode is displayed. Figure 1 Testcase Application Command

3.2 Blank Pack -Run ()

The problem to be solved is to give developers a convenient "place" for placing their fixt code and test code. Stating TestCase as Abstract means that developers want to reuse TestCase through Subclassing. However, if all of us can provide a superclass with only one variable and there is no behavior, it will not be able to do too much work to meet our first goal - make the test easier to write.

Fortunately, all tests have a common structure - establish a test fixture, run some code on the fixture, check the result, and clean the clamp. This means that each test will run with a new fixture, and the result of a test will not affect the results of other tests. This supports the target maximized target.

Template Method is better involved in our problem. Take it intended, "Define a skeleton of an operation algorithm and delay some steps into subclasses. Template Method allows subclasses to redefine certain specific steps of the algorithm without changing the structure of an algorithm." This is complete appropriate. We just want developers to consider how to write fixtures (establish and disassemble) code, and how to write test code. In any case, this order will remain the same for all tests, regardless of how the clamp code is written or how to write it.

The Template Method is as follows:

Public void run () {

setup ();

Runtest ();

TEARDOWN ();

}

These methods are implemented as "nothing" by the default:

protected void runtest () {

}

protected void setup () {

}

protected void teardown () {

}

Since SETUP and TEARDOWN are used to override, and they will be called by the framework, we declare them as protected. Our second snapshot shown in Figure 2.

Figure 2 Testcase.Run () Application Template Method

3.3 Results Report - TestResult

If a TestCase is running in the forest, is there anyone care about its results? Of course - your running test is to confirm that they can run. After the test is running, you want a record, a summary of how you failing to work.

If the test has equal success or failure, or if we just run a test, we may just set a flag in the TestCase object, and when the test is complete, look at this logo. However, test (often) is very uneven - they usually work. So we just want to record failed, and a highly concentrated summary of success. The SmallTalk Best Practice Patterns (see BECK, K. Smalltalk Best Practice Patterns, Prentice Hall, 1996) has a mode, called Collecting Parameter (collecting parameters). It recommends that when you need to collect results between multiple methods, you should add a parameter to the method and pass an object to collect results for you. We create a new object, TestResult, to collect the results of the running test.

Public Class TestResult Extends Object {

Protected int fruntests;

Public testResult () {

Fruntests = 0;

}

}

This simple version of TestResult can only calculate the number of running tests. In order to use it, we have to add a parameter in the testcase.run () method, and notify TestResult that test is running:

Public void Run (TestResult Result) {

Result.startTest (this);

setup ();

Runtest ();

TEARDOWN ();

}

And TestResult must remember the number of tests running:

Public Synchronized Void StartTest (Test Test) {

Fruntests ;

}

We declare the TestResult's Strattest method as synchronized, so that when the test runs in different threads, a separate TestResult can be stored safely. Finally, we want to keep TestCase simple external interfaces, so create a non-arranging Run () version, which is responsible for creating its own TestResult.

Public TestResult Run () {

TestResult result = cretesult ();

Run (result);

Return Result;

}

Protected testResult cretesult () {

Return New TestResult ();

}

The following design snapshots can be shown in Figure 3.

Figure 3 TestResult Application Collecting Parameter

If the test is always able to run correctly, then we will not need to write them. The test is only interested when testing fails, especially when we do not expect them to fail. What's more, testing can fail with the way we expect, for example by calculating an incorrect result; or they can fail in more attractive ways, such as by writing a array. Regardless of how the test fails, we want to perform the back test.

JUnit distinguishes a failures and errors (ERRORS). The possibility of failure is expected and inspected in use with an assertion. The error is an unpredictable problem, such as ArrayIndexoutofboundSexception. Failure can be sent via an assertionfailerror. In order to recognize an unpredictable error and a failure, failure will be captured in the Catch clause (1). The clause (2) captures all other exceptions and ensures that our test can continue to run ... public void run (test result) {

Result.startTest (this);

setup ();

Try {

Runtest ();

}

Catch (assertionfailederror e) {// 1

Result.addfailure (this, e);

}

Catch (throwable e) {// 2

Result.Adderror (this, e);

}

Finally {

TEARDOWN ();

}

}

The Assert method provided by TestCase triggers an AssertionFaileDError. JUnit provides a set of Assert methods for different purposes. The following is just the simplest one:

Protected void assert (Boolean Condition) {

IF (! condition)

Throw new assertionfailederror ();

} ([Translator Note] Since the keyword ASSERT conflict with the keyword in JDK, the AssertTrue is changed to asserttrue in the latest JUnit release.)

AssertionFaileDerror should not be captured by a customer (one test method in TestCase), but should be responsible by TestCase.Run () inside the Template Method. So we will derive the AssertionFaileDError to Error.

Public class assertionfailedError extends error {

Public assertionfailedError () {}

}

The method of collecting errors in TestResult can be as follows:

Public Synchronized Void Adderror (Test Test, Throwable T) {

Ferrors.Addelement (New Testfail (Test, T));

}

Public Synchronized Void AddFail (Test Test, Throwable T) {

FFailures.Addelement (New Testfail (Test, T));

}

TestFail is a small frame-in-one help class that will fail the test and the anomalies that send signals to subsequent reports.

Public Class Testfailure Extends Object {

Protected test ffailedtest;

Protected throwable fthrownexception;

}

The Collecting Parameter mode of the specification requires that we will pass Collecting Parameter to each method. If we follow this suggestion, each test method will require TestResult parameters. It will result in "contamination" of these method signs (SIGNATURE). Use an exception to send fails as a friendly side effect, so that we can avoid such signature pollution. A test case method, or a Helper Method that is called, you can throw an exception without knowing TestResult. As a training material, a simple test method is given here, and it comes from our Moneytest Suite ([Translator Note] See another article included in the JUnit release version. It demonstrates how a test method does not have to know any information about TestResult. Public void testmoneyequals () {

ASSERT (! f12chf.equals (null);

askERTEQUALS (F12CHF, F12CHF);

Assertequals (F12CHF, New Money (12, "CHF"));

Assert (! f12chf.equals (f14chf));

} ([Translator Note] Since the keyword ASSERT conflict with the keyword in JDK, the AssertTrue is changed to asserttrue in the latest JUnit release.)

JUnit proposes different implementations on TestResult. Its default implementation counts and collects results for failure and errors. TextTestResult collects results and express them in the form of a text. Finally, the graphic version of the JUnit Test Runner uses UitestResult to update the graphical test status.

TestResult is an extension point of the frame. Customers can customize their TestResult classes, such as HTMLTestResult to report the results as an HTML document.

3.4 Not stupid Subcador - Review Testcase

We have applied Command to express a test. Command relies on a single method like Execute () (called Run () in TestCase) to call it. This simple interface allows us to call a common implementation of a Command through the same interface.

We need an interface to run our test. However, all test cases are implemented as different methods of the same class. This avoids unnecessary classes of classes. A given test case class (TEST CASE CASS) can achieve many different methods, each of which defines a separate test case (TEST CASE). Each test case has a descriptive name, such as TestMoneyEquals or TestMoneyAdd. Test cases do not meet simple Command interfaces. Different instances of the same COMMAND class need to be called with different methods. So the question below is that all test cases are the same from the perspective of the test caller.

Review the problems involved in the currently available design model, Adapter (adapter) mode is included in the mind. Adapter has the following intentions "convert a class interface into another interface that customers want." This sounds very suitable. Adapter tells us how different ways do. One of them is the Class Adapter, which uses subclasses to adapt the interface. For example, in order to adapt TestMoneyEquals to RUNTEST, we implements a subclass of MoneyTest and rewrite the RUNTEST method to call TestMoneyEquals. Public class testmoneyequals extends Moneytest {

Public TestmoneyEquals () {Super ("TestmoneyEquals");}

Protected void runtest () {TestMoneyEquals ();

}

Subcarrelation requires we implement a subclass for each test case. This places an additional burden to the tester. This is contrary to JUnit's goal, that is, the framework should make the increase in test cases simply as possible. In addition, create a subclass for each test method, causing Class Bloat. Many classes will have only one separate method, which is not worth it, and it is difficult to make meaningful names.

Java provides an anonymous inner class, which provides an interested Java specialized solution to solve the name of naming. Through anonymous internal classes, we can create an Adapter without having to create a name of a class:

Testcase test = new Moneytest ("TestMoneyEquals") {

Protected void runtest () {TestMoneyEquals ();

}

This is much more convenient than complete subcategory. It is a cost-time type checking that is based on some of the developer's burden as a cost-time type checking (Compile-Time Type Checking). SmallTalk Best Practice Pattern describes additional scenarios to solve different instances of problems that are different performance in a common PLUGGABLE BEHAVIOR title. This idea is to perform different logic using a separate parameterized class without subclass.

The simplest form of Pluggable Behavior is a PlugGable Selector (plug-in selector). Pluggable Selector saves a SMALLTALK Selector method in an instance variable. This idea is not limited to SMALLTALK, which is also applicable to Java. There is no marker of a Selector method in Java. However, Java Reflection API allows us to call this method according to a representation of a method name. We can use this feature to implement a Java version of the Pluggable Selector. In terms of open topics, we usually do not use reflections in the usual application. In our case, we are processing a infrastructure framework, so it can wear reflection hats.

JUnit allows customers to choose itself, using a Pluggable Selector, or implements the anonymous Adapter class mentioned above. For this reason, we provide Pluggable Selector as the default implementation of the Runtest method. In this case, the name of the test case must be consistent with the name of a test method. As shown below, we use reflection to call the method. First we will find the Method object. Once we have a Method object, it will call it and pass its parameters. Since our test methods do not have a parameters, we can pass an empty parameter array. Protected void runtest () throws throwable {

Method Runmethod = NULL;

Try {

Runmethod = getClass (). getMethod (FName, New Class [0]);

} catch (nosuchmethodexception e) {

Assert ("Method /" FNAME "/" Not Found ", False;

}

Try {

Runmethod.Invoke (this, new class [0]);

}

// catch invocationTargeTexception and iLlegaCCESSEXCEPTION

}

JDK1.1 Reflection API only allows us to find public methods. Based on this reason, you must declare the test method as public, otherwise you will get a NosuchMethodeXception exception.

In the following design snapshot, add Adapter and Pluggable Selector.

Figure 4 TestCase Application Adapter (with an anonymous internal class) or pluggable selector

3.5 Don't care about one or more - Testsuit

In order to obtain confidence in the system status, we need to run a lot of tests. Up to now, JUnit can run a separate test case and report the result in a TestResult. Our next challenge is to expand it so that it can run many different tests. This problem can be easily resolved when the test caller does not have to care about it is one or more test cases. One popular mode that can spend the difficult gate in this case is Composite. Excerpts to "combine the object into a tree structure to represent the hierarchy of the 'part - overall'. Composite makes the user consistent with the use of a single object and combination objects." Here 'part - the hierarchy of the overall' is Places that people are interested in. We want to support test kits that can be laminated.

Composite introduces the following participants:

N Component: Statect the interface we want to use to interact with our tests.

n Composite: Implement the interface and maintain a collection of tests.

n Leaf: Represents a test case in Composite, which is compliant with the Component interface.

This mode tells us to introduce an abstract class to define a public interface for separate objects and Composite objects. The basic intent of this class is to define an interface. When applying Composite in Java, we prefer to define an interface instead of an abstraction class. Use the interface to avoid submitting JUnit into a specific base class for testing. It is necessary for these tests to comply with this interface. So we do this to the model, and introduce a TEST interface:

Public interface test {

Public Abstract void Run (TestResult Result);

}

Testcase corresponds to a Leaf in Composite, and implements the interface we have seen above. Below, we introduce Participant Composite. We named TestSuit (Test Suite) class. Testsuit saves its sub-test in a vector:

Public Class TestSuite Implements = New Vector (); DELEGATE: PUBLIC VOID RUN (TestResult Result): Public Void Run (ENUMERATION E = ftests.ersion); E .hasMoreElements (); Test test = (test) E.NEXTELEMENT (); test.run (result);}}

Figure 5 Testsuit Apps Composite

Finally, the customer must be able to add a test to a kit, which will use the AddTest method to do this:

Public Void AddTest (Test Test) {

Ftests.addeElement (Test);

}

Note How all the code is only dependent on the Test interface. Due to TestCase and TestSuit compliant with the Test interface, we can recursively combine the test kit into a kit. All developers can create their own Testsuit. We can create TestSuit combined with these kits to run them.

Here is an example of creating Testsuit:

Public static test suite () {

Testsuite suite = new testsuite ();

Suite.Addtest (New Moneytest ("TestMoneyEquals");

Suite.Addtest (New Moneytest ("TestsImpleAdd"));

}

This will work very well, but it needs to be manually added to a kit manually. Early JUnit adopters tell us that it is stupid. As long as you write a new test case, you must remember to add it to a STATIC's suit () method, otherwise it will not run. We added a convenient construction method for TestSuit, which will test the case class as a parameter. It is intended to extract the extract test method and create a kit containing these test methods. A simple agreement that the test method must follow is that the prefix "Test" begins and without parameters. The convenient construction method uses this agreement to construct a test object by using a reflection discovery test method. Using this constructor, the above code will be simplified:

Public static test suite () {

Return New TestSuite (MoneyTest.Class);

}

When you just want to run a subset of the test case, the initial way will remain useful.

3.6 Summary

Now we are located in JUnit walking on the end of the horse. The JUnit design is elaborated by the angle of the pattern, as shown below.

Figure 6 JUnit mode summary

Note Testcase as the center of the frame, which is related to four modes. The description of the mature object design shows this same "mode density". The center of the design is a rich relationship, which is associated with the supported participants (Player).

This is another way to look at all the modes in JUnit. On this episode board, the impact of each mode is abstractly represented in turn. Thus, the Command mode creates a TestCase class, and the Template Method mode creates a RUN method, and so on. (The tag of the plot is deleted on the basis of the tag in Figure 6). Figure 7 JUnit mode plot board

It is important to note that the complexity of the plot is how the complexity of the map is jumped when we apply Composite. It confirms our intuition in the way, that is, Composite is a powerful mode, but it will "make the map complicated." It should be carefully used.

4 Conclusion

Finally, let us make some comprehensive observations:

mode

We found that the design is very valuable from the perspective of the model, whether in the development of our framework, or we try to discuss it to others. You are now in a perfect location to determine if you describe if a framework is valid. If you like the above discussion, please try the same performance style for your own system.

Mode density

The mode "density" surrounded by Testcase is relatively high, which is the key abstraction of JUnit. The high-mode density design is more easy to use, but it is more difficult to modify. We have found that such a high mode density around the key abstraction is common for mature frameworks. Its opposite should be applied to those immature frames - they should have low mode density. Once you find the question you want to solve, you can start "Compress" solution until a mode is increasingly dense, and these modes provide the role of the leverage.

Use what you do

Once we have completed the basic unit test function, we should apply it itself. TestCase can verify the correct results for errors, success, and failure reports. We found that as the frame design continues to evolve, this is invaluable. We found that JUnit's most challenging app is to test its own behavior.

Intersection (InterSection)

), Rather than pairs (Union

)

There is a temptation in the framework development that contains the features you can have. After all, you want to make the frame as much value as possible. However, there will be an obstacle - developers have to decide to use your framework. The less characteristics of the frame, the easier it is, the more likely, the developer uses its possibility. JUnit is written according to this style. It only implements the fully basic characteristics of test run - run the test, so that the execution of each test is isolated from each other, and the test is automatically run. Yes, we can't resist the addition of some features, but we will carefully put it in their own extension (Test.extensions). A member worth noting in this package is TestDecorator that allows additional code to be executed before and after a test.

Frame writer to read their code

We spend more time on reading JUnit's code than writing it. Moreover, it is almost equal to the time that the repetition function is almost equal to the time of adding new features. We actively design experiments to add new classes and mobile responsibilities in a variety of different ways we can think of. Through the continuous insight into JUnit (test, object design, framework development), as well as the opportunity to publish a deeper article, we have gained returns because of our paranoia (and will remain returned).

The latest version of JUnit can be

ftp://www.Armaties.com/d/Home/Armaties/ftp/testingframework/junit/ Download.

5 thank you

Thank John Vlissides, Ralph Johnson and Nick Edgar, thanks to their careful reading and good meaning. Reference:

JUnit a cook's tour, www.junit.org

[Gof95] Erich Gamma etc., Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995. 中国 本: "Design mode: can be used for object-oriented software foundations", Li Yingjun and other translations, Machinery Industry Press September 2000.

转载请注明原文地址:https://www.9cbs.com/read-36073.html

New Post(0)