Test Doubles Test Doubles: Testing in Distributed Systems and Real World

What is Test Doubles

In software testing, we developed unit tests and integration tests to test the code's functionality. However, in the real world, it is very common for a piece of code to interact with external components, for example, databases, public APIs, or other computers. n the real world with distributed computing, challenges are everywhere, systems will have to handle things like black Friday user traffic, lightning hit the data centers. To build robust software, our tests should cover all possible cases. As behaviors of external dependencies are hard to control, test doubles are introduced to replace production objects to reduce complexity and facilitate testing. Test doubles are useful not only to test unlikely events, but also isolates normal tests from the unpredictability of the real system.

"Test Doubles" is a technique used to aid unit testing. The name was first proposed by Gerard Meszaros, who likened it to a stuntman in film production. When we unit test, we usually focus on testing a specific function or method. However, these functions or methods often depend on other objects or systems. For example, a function might need to query data from a database, or call another service's API. In this case, these dependencies can lead to complex or unpredictable tests. To solve this problem, we can use "Test Doubles" to simulate these dependencies.

Types of Test Doubles

The types of "Test Doubles" mainly include the following: Fakes, Stubs, Mocks.

Fakes

Fakes are simplified versions of real objects. They are fully functional, but implemented in a simpler or more controlled manner. For example, you might create a fake database, which might just store data in memory instead of on disk.

Stubs

Stubs are artificial classes pre-configured to return data. They don't care how many times they are called or with what parameters, they simply return pre-configured responses. For example, you might create a Stub web service that always returns a fixed response regardless of the content of the request.

It can be said that stub is more trouble-free than fake, and the return value is directly booked without even installing it. 

Mocks

Mocks are variants of real classes that allow for fine-grained control. Unlike Stubs, Mocks can check how many times they were called, and with what arguments. Mocks are often used to verify that your code is interacting with dependencies correctly. For example, in an email service, we don't want to send an email every time we run a test. Also, it's not easy to verify in tests that the correct email is being sent. The only thing we can do is verify that the sendEmail() method was called. We can write a mock method for sendEmail(), this mock method will help us record whether it is called by our application. This way, we can verify that our code is calling the send email method correctly without actually sending the email.

Other Benefits of Using Test Doubles

Potential failures in third-party APIs are not the only reason we use test doubles. There are also these benefits of using test doubles:

  • Speed/performance: APIs can be slow (network traffic, large databases, etc.). A good test suite executes quickly, which allows us to conduct more test scenarios.
  • High coverage: such as API changes, slow response, timeouts, errors, etc. We can all simulate it through mocking.
  • Stability: Ensure the certainty of the returned results and reduce the instability of the test.
  • For development: Assuming that we have not implemented a function, we can also replace it with Test double first. At this time, the role of test double is not testing but development, and it is not a bad idea.

Practice of Test Doubles in Java

1 Creation of Fakes

The creation of Fakes is simple and well understood. We actually just need to create a new class which should implement the same interface as the original class. In this way, we have this "stand-in" to replace the original class. We then write a corresponding working implementation in this class, but it should be cleaner and faster than the original implementation.

2 Creation of Stubs and Mocks

To create Stubs and Mocks we can use a Java framework called Mockito .

2.1 An example of using Mockito to create a stub:

// 1 - create mock object
LinkedList mockedList = mock(LinkedList.class);
// 2 - define stubbing return value before actual execution
when(mockedList.get(0)).thenReturn("first");
// 3 - call methods
// 3.1 - the following prints "first"
System.out.println(mockedList.get(0));
// 3.2 - the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));

In this example, we have defined in advance that if get(0) is called, it will return "first", and it will automatically return null if it is not predefined.

Here is an example of creating a stub with a callback function:

when(mock.someMethod(anyString())).thenAnswer(
    new Answer() {
        public Object answer(InvocationOnMock invocation) {
            Object[] args = invocation.getArguments();
            Object mock = invocation.getMock();
            return "called with arguments: " + Arrays.toString(args);
        }
    });

whenA function can accept an Answerobject as a parameter, which Answerdefines how it should respond when the expected method is called. In this example, when someMethodthe function is called, we return a string containing all parameters.

Mockito provides a series of doXxxfunctions, such as doThrow(), doAnswer(), doNothing(), doReturn()and doCallRealMethod(), which can be used instead of whenfunctions to define the behavior when the expected method is called.

2.2 An example of using Mockito to create a Mock

// 1 - create mock object
List mockedList = mock(List.class);
// 2 - call methods on mock object
mockedList.add("one");
mockedList.clear();
// 3 - verify the methods are called
verify(mockedList).add("one");
verify(mockedList).clear();
// 3.1 - verify the number of times a method is called
verify(mockedList, times(1)).add("one");

We can see that in addition to verifying whether the method has been called, we can also see the number of times the method is called, which is the feature of Mock. In this code, it is assumed that add("one")the method has not been called. When you execute it verify(mockedList).add("one"), Mockito will throw an exception, indicating that the expected call did not happen. If add("one")the method was called correctly, then verifythe method will take no action and the test will continue.

In addition to times, verify can also accept other parameters, for example, never()it means that it is not expected to be called, atMostOnce()it means that it is called at most once, and atLeast(2)it means that it is called at least twice. If the actual number of calls is not as expected, verifyan exception will be thrown.

verify(mockedList, never()).add("never happened");
verify(mockedList, atMostOnce()).add("once");
verify(mockedList, atLeast(2)).add("three times");

Reference Finally, an English reference is attached:

https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da

Guess you like

Origin blog.csdn.net/weixin_44492824/article/details/130633917