Detailed design and implementation of unit test
Unit test principle:
- The execution results are automatically given through A series of assertions without human judgment (Alibaba development manual stipulates that no output is allowed to judge by naked eyes) (A)
- Test cases cannot depend on each other and are independent (I)
- Unit tests can be executed repeatedly and cannot be affected by external environment, such as database, remote call, middleware and other external dependencies, which cannot affect the execution of test cases (R)
Based on the above principles, in the one-sided stage, we do not rely on the Spring container as much as possible, but mock external dependencies, so as to achieve faster testing.
Frame selection
Based on the above comparison, we can choose Alibaba's TestableMock or Mockito+Powermock. Take the second scheme as an example.
Test preparation
Naming conventions:
There are no special requirements for naming in Mockito and Powermock, but we'd better establish the same path of the tested class in the Test directory, in the main directory, and name it after the tested class + Test. This has a good shortcut key generation in idea. (the shortcut keys Ctrl + Shift + T or Test in Generate can quickly Generate the Test class of the corresponding directory under Test.)
Dependency:
Usually, we just need to introduce the spring boot starter test dependency in the spring project, which includes some common modules such as Junit, Spring Test, AssertJ, Hamcrest, Mockito, etc. Other items can be imported by themselves.
Test process
Writing a single test generally includes three parts
1. Given (Mock external dependency & prepare Fake data)
2. When (call the tested method)
3. Then (assertion execution result)
case analysis
@Service public class UserService { @Autowired private UserDao userDao; private UserMsg userMsg; public String saySomething() { String user = userDao.getUserName(); String key = userMsg.getKey(); return user + key; } }
@RunWith(MockitoJUnitRunner) public class UserServiceTest{ @InjectMocks private UserService userService; @Mock private UserDao userDao; @Mock private UserMsg userMsg; public void saySomething(){ //Given when(userDao.getUserName()).thenReturn("Xiao Ming"); when(userMsg.getKey()).thenReturn("like to study"); //When String message = userServic.saySomething() //Then assertNotNull(message);//Assertion is not empty assertEquals(message,"Xiao Ming loves learning");//Assert equality verify(userDao).getUserName();//The validation method was executed verify(userMsg,times(1)).getKey();//Number of validation method executions //Verify execution sequence InOrder inOrder = Mockito.inOrder(userDao,userMsg); inOrder.verify(userDao).getUserName(); inOrder.verify(userServic).getKey(); } }
matters needing attention
Limitations of Mocktio:
Cannot mock static methods
Cannot mock private method
Cannot mock final method
Cannot mock constructor
Solution:
PowerMock is developed based on Mockito, and its syntax rules are basically consistent with Mockito
You can use PowerMock to complete the Mock of private/static/final and construction methods.
PowerMock use
Relevant notes:
@The RunWith(PowerMockRunner.class) annotation indicates that test cases are run using PowerMockRunner
@The PrepareForTest({NodeScheduler.class, NodeService.class,}) solution is used to add all classes to be tested. Here are two classes to be tested.
Add dependency:
testImplementation("org.powermock:powermock-api-mockito2:2.0.9")
testImplementation("org.powermock:powermock-module-junit4:2.0.9")
Mock final
public class BookDao { public final boolean stored(Book book) { System.out.println("......confirm whether specified book is stored by BookDao......"); return true; } }
@RunWith(PowerMockRunner.class) @PrepareForTest({BookService.class, BookDao.class}) public class BookServiceTestWitPowerMock { @Test public void isStored() { //mock final Book book = PowerMockito.mock(Book.class); BookDao bookDao = PowerMockito.mock(BookDao.class); PowerMockito.when(bookDao.stored(book)).thenReturn(false); } }
Mock private
public class BookService { private boolean checkExist(String name) { System.out.println("---BookService checkExist---"); throw new UnsupportedOperationException("UserService checkExist unsupported exception."); } }
@RunWith(PowerMockRunner.class) @PrepareForTest(BookService.class) public class BookServiceTestWithPowerMock { @Test public void exist() throws Exception { //mock private BookService bookService = PowerMockito.spy(new BookService()); PowerMockito.doReturn(true).when(bookService, "checkExist", arg); }
Mock static
public class BookDao { public static void insert(Book book) { throw new UnsupportedOperationException("BookDao does not support insert() operation."); } }
@RunWith(PowerMockRunner.class) @PrepareForTest({BookService.class, BookDao.class}) public class BookServiceTestWithPowerMock { @Test public void count() { //Mock static PowerMockito.mockStatic(BookDao.class); PowerMockito.when(BookDao.count()).thenReturn(10); } }
Mock New
public class UserService{ public void saveUser(String username, String password){ User user = new User(username,password); user.insert(); }
@Test public void saveUser() throw Exception{ String username = "user1"; String password = "aaa"; //mock new User user = PowerMockito.mock(User.class); PowerMockito.wenNew(User.class).withArguments(username,passsword).thenReturn(user); ..... }
Coverage
- Idea comes with
Coverage framework
JaCoCo
Configuration in gradle
Execute gradle test to find the report in the build \ reports \ Jacobo directory.
matters needing attention
When JaCoCo and PowermMock are used together, there will be a conflict because JaCoCo ignores the classes in the annotation @ PrepareForTest({}). The solution is to use JaCoCo's offline mode.