Test tips for savvy Java developers

Source: Internet
Author: User
Tags stub

We all write tests for our code, don't we? There is no doubt that I know the answer to this question may be from "of course, but do you know how to avoid writing tests?" "To" must! I love tests "all have. Next I'll give you a few tips that will make it easier for you to write tests. That will help you reduce weak testing and make your application more robust.

At the same time, if your answer is " No, I don't write tests ." ", then I hope these simple but effective techniques can help you understand the benefits of writing tests. You will also see that writing a complex, worthless test set (suit) is not as difficult as you think.

How to write tests and what best practices for managing test collections are not new. We have discussed many times in some aspects of the problem in the past. From "The right way to use integration testing during the build process" to talk about "properly simulating the environment in unit tests", to " code coverage and how to find the code that you really need to test".

But today I want to talk to you about a series of tips that can help you figure out how the test works from the bottom up in your head. From how to construct a simple unit test to a higher level of understanding of mock (mock) and Spy (watch) and copy-paste test code. So let's get started.

Aaarrr, talking like a pirate?

Like most software development, patterns are usually a good start. Whether you want to create objects from a factory, or want to spread the focus of your Web application into model, view, and controller, there is usually a pattern behind them to help you understand what is going on and solve the problem. So what does a typical test look like?

When we write tests, one of the most useful but extremely simple patterns is the plan-execute-assert (arrange-act-assert), or AAA.

The premise of this pattern is that all tests should follow the default layout. All the conditions and inputs necessary to test the system should be set at the beginning of the test method (Arrange). After all the preconditions have been planned, we run (ACT) on the test system by triggering a method or checking some state of the system. Finally, we need to assert that (Assert) The test system has generated the desired results.

Let's take a look at an example of a Java JUnit test that shows this pattern:

1234567891011 @Testpublic void testAddition() {    // Arrange    Calculator calculator = new Calculator();    // Act    int result = calculator.add(1, 2);    // Assert    assertEquals("Calculator.add returns invalid result", 3, result);}

See how accurate the code flow is! Plan-Execute-assert mode allows you to quickly understand the functionality of the test. It's easy to write very bad code when you deviate from this pattern.

Remember the law of Dimitri

The Dimitri law applies the minimum knowledge principle to the software and reduces the coupling of the unit-this has been the design goal of the software development.

The Dimitri Law can be expressed as a series of rules:

    • In a method, an instance of a class can call other methods of that class;
    • In the method, the instance can query its own data, but cannot query data data (translator Note: That is, when the data of the instance is more complex, the query cannot be nested);
    • When a method receives a parameter, the first-level method of the parameter can be called;
    • When a method creates an instance of some local variables, an instance of the class can invoke the methods of these local variables;
    • Do not call methods of global objects.

So what does this mean in terms of testing? Well, since the Dimitri law reduces the coupling between parts of the application, it means that it's easier to test parts of the application. To see how the law can help with testing, let's look at a class that is poorly defined, and it violates the Dimitri rule:

Consider the following class that we want to test:

12345678910 public class Foo() {    public Bar doSomething(Baz aParameter) {        Bar bar = null;        if (aParameter.getValue().isValid()) {            aParameter.getThing().increment();            bar = BarManager.getBar(new Thing());        }        return bar;    }}

If we try to test this method, we will soon find some problems. These problems are caused by the way the methods are defined.

The first difficulty we will encounter when testing this method is that we call a static method--Barmanager.getbar (). We have no way of simply specifying how to operate this method in a unit test. Remember the plan-execute-assert pattern we mentioned? But here, before we execute this method by calling DoSomething (), we do not have an easy way to set the Barmanager. If Barmanager.getbar () is not a static method, you can pass in a Barmanager instance to the DoSomething () method. In a test set, it is very easy to pass a sample value (sample values), and we can better control and predict the execution of the method.

We can also see that the method chain has been called in this example method: Aparameter.getvalue (). IsValid () and aparameter.getthing (). Increment (). To test them, we need to know clearly the return type of Aparameter.getvalue () and aparameter.getthing () before we can build the appropriate analog values in the test.

If we want to do this, we have to understand the details of how these methods return objects. And our unit tests begin to morph into a bunch of maintainable, fragile code. We're breaking a basic rule in unit testing: test only individual units, not the implementation details of the unit.

I'm not saying unit tests can only test individual classes. In most cases, however, it may be a good idea to consider a class as a separate unit. But in some cases, we will consider two or more classes as a unit.

Here I leave an exercise for all of you: complete refactoring of this method to make it easier to test. But for beginners, we may pass the Aparameter.getvalue () object as a parameter to this method. This will satisfy some rules and improve the testability of the method.

Understanding when to use assertions

JUnit and testng are excellent frameworks for writing application tests, and they provide many different ways to assert a value in a test. For example, check whether two values are the same or different, or if the value is empty.

Well, now that you've agreed to assert that it's cool, let's use it anytime, anywhere. Wait, excessive use of assertions can make testing brittle, resulting in an inability to maintain. Once this is done, we know exactly what the result behind it is-the code that cannot be tested and is not stable.

Consider the following test example:

123456789101112131415 @Testpublic void testFoo {    // Arrange    Foo foo = new Foo();    double result = …;    // Act    double value = foo.bar( 100.0 );    // Assert    assertEquals(value, result);    assertNotNull( foo.getBar() );    assertTrue( foo.isValid() );}

At first glance, there's nothing wrong with this piece of code. We followed the AAA model and asserted something that had happened-so what was wrong?

First, we see the name of this test: Testfoo, which does not really tell us what the test is doing, and does not match any of the assertions we are checking.

Then, if one of the assertions fails, can we determine which part of the test system failed? is the Foo.bar (100.0) method failed? Or did the Foo.getbar () or Foo.isvalid () method fail? If we try to find out what's going on without testing internal debugging, we don't know.

The purpose of the gimbal test is that we want a reliable, robust test set . By running them quickly, we can know the state of the application. The trouble with this example is that our purpose has been dashed. If the test fails, we have to run the debugger to find out where the failure is, and our situation becomes difficult.

In general, a best practice is to have only one of the most appropriate assertions in a particular test. This way we can make sure that the test is clear and that the target is a single functional point of the application.

Spy, mock and stub, God!

Sometimes it is useful for a spy application to do something, or a validator to invoke a specific method with a specific parameter and invoke a specified number of times. Sometimes we want to trigger the database layer, but we want to simulate the response that the database returns to us. With the help of spies, mocks, and stubs, we can implement all of these features.

In Java, we have many different libraries that can be used for spies, mocks, and stubs, such as Mockito, Easymock, and Jmockit. So what's the difference between a spy, a mock, and a stub? When should we use them?

The spy can make it easy for you to check whether a program has called some methods with the correct parameters, and log these parameters for later validation to use. For example, if you have a loop in your code that triggers a method in each loop, the spy can be used to verify that the method is triggered correctly, and that the correct incoming parameters are used each time the trigger is triggered. For some specific types of stubs, the spy is critical.

A stub is an object that provides a specific, stored response when a client triggers a request, such as a response that has been generated by pre-programmed for the input stub. Stubs are useful when you want to force certain conditions in a code snippet, for example, if the database call fails and you want to trigger database exception handling in the test. A stub is a special case of a mock object.

A mock (mock) object provides all the functionality of a stub object, and it also provides pre-programmed expected results. This means that the mock object is very close to the real object, and it can perform different behaviors based on the previously set state. For example, we can use a mock object to represent a security system that provides different access controls depending on which users are logged on. As far as our tests are concerned, it interacts with a real security system, and we can test many different paths in the application.

Sometimes we use the word test Doubleto represent any type of object as described above, and we interact with those objects in our tests.

In general, thespy provides the least amount of functionality because it is intended to be a method of capturing whether or not it is called. If called, what parameters are passed in.

stubs are the next level of test aliases that set the execution flow of the test system by setting a predefined method call return value. A particular stub object can often be used in many tests.

Finally,mockobject (mock objects) provides more behavior than a stub object. In this regard, a best practice is to develop specific stub objects for specific tests, or the stub objects will begin to become complex like real objects.

Don't let your testExcessive dry

In the software development process, it is often a best practice to have your application DRY(do not repeat itself,Don ' t Repeat Yourself).

In testing, this is not always the case. When writing software, a best practice is to refactor those common code snippets into separate methods that can be called many times in code. This makes sense because we only write the code once, and then we just need to test it once. Also, if we only need to write the code snippet once, we can avoid spelling mistakes that have been written many times. be careful to copy and paste!

In 2006, Jay fields created a new word: damp (Descriptive And Meaningful Phrases, descriptive and meaningful phrases), It is used to refer to those well-designed domain-specific languages. If you want to recall again, you can refer to the original mail: DRY code, damp DSL.

The rationale behind damp is that, for a good domain-specific language, it uses descriptive and meaningful phrases to increase the readability of the language and reduce the learning and training time required for efficient use of the language.

Often, many unit tests in a test set may be very similar, and the only minor difference is in how to prepare the test system for testing. Therefore, it is natural for software developers to refactor these duplicated code from unit tests to helper functions. It is also natural to re-create the instance variables as static variables so that they can be declared only once for each test class-and once again remove duplicate code from the test.

Although the code becomes more "neat" after the refactoring is done, the unit tests become more difficult to read as a separate part. If a unit test calls several other methods, and the non-local variables are used, then the process of unit testing becomes not intuitive, and you are not able to understand the basic process of unit testing as easily as before.

Crucially, if we let our unit test dry, then the complexity of the test would be higher, and the maintenance of the test would be more difficult--which would be contrary to the original intention of the test dry. For unit tests, making them more damp, rather than dry, increases the readability and maintainability of the test.

We don't have the right or wrong answers about the extent to which you should refactor your tests, but we're trying to balance the tests too dry and too damp, which is sure to make our tests easier to maintain.

Conclusion

In this article, I've covered five basic principles that will help us write unit tests for our applications. If you have any ideas, please share them via the comments below, or you can find me on Twitter: @cocoadavid.

Hopefully you can hope that we have discussed these principles and can see how they are subtly giving you the passion to write unit tests. Yes, I mean "love" because I believe writing unit tests is a basic requirement for high-quality software.

High-quality software means satisfying users, while satisfying users means happy developers.

Development Happy!

Original link: Zeroturnaround translation: importnew.com-wing
Link: http://www.importnew.com/16392.html
[ Reprint please keep the source, translator and translation links.] ]

Test tips for savvy Java developers

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.