编程知识 cdmana.com

Ask questions and answer questions! This is the correct way to understand the code

Last one We use the call relationship , Sort out TestRunner The process of invoking the core model .

This is 《 How to read the source code efficiently 》 Chapter 11 of the topic , Let's answer some questions in the process , Think about why you design like this .

In the last article, we raised several questions :

  • Why use Statement class ? What is the role ?

  • RunNotifier How to monitor ?

  • classBlock In the method ,if What is the use of logic in judgment ? It seems that the method name is similar to BeforeClass、AfterClass Notes have something to do with , How did it deal with it ?

  • Why use Statement Encapsulate a layer to perform tests ? All the methods are in ParentRunner In class , Just call it directly ?

  • runChildren Method, why build a Runnable To carry out ?

This section will answer these questions in the future .

Statement The role of

Actually , If you are familiar with design patterns , You should be able to recognize it immediately ,Statement It implements a command mode .

And what is the function of command mode ? Encapsulate a request as an object , This allows you to parameterize customers with different requests ; Queues or logs requests , And support cancellable operations .

This is the same. Statement The role of .Statement Perform different test behaviors through a unified object .
Then why use command mode ?

Let's think back to , When we were performing the test , The execution process of each test is not exactly the same ? Sometimes we perform a single test method , And sometimes we may execute a test class . At the same time, the execution process of each test method is not exactly the same , Some methods are Before Method or After Method to execute , And some don't ; Some methods are BeforeClass and BeforeClass To execute , Other methods do not . These different behaviors are through Statement To encapsulate .

that Statement How to package it ? Let's look back classBlock Method :

First childrenInvoker Method ( See the picture below ) Directly build a Statement Anonymous implementation class of , To wrap and execute all qualified test methods in the class .
Then passed if The four methods in the decision add additional execution logic .

Limited to space , Let's just look at the first withBeforeClasses Method , Look at the name , We should be able to guess that this method is to deal with being BeforeClass Annotation method .

Before looking at its implementation , We can think about it , If we come to realize it ? How can we achieve it ? Or we can ask another way , Is there any way to ensure that one method executes before another ? Do you have any ideas ?( Before looking down , You'd better think about it yourself )

such as , We can use a Statement Packaging , That is to use decoration mode . In the execution of this Statement Before , Execute first BeforeClass; Or we can use the combination mode , Construct a parent Statement,BeforeClass And the original Statement As a leaf node , But pay attention to the order here .

Now let's take a look at JUnit How does it come true ?

First , adopt TestClass Object's getAnnotatedMethods Method to find all that have BeforeClass Method of annotation . If there is no corresponding annotation method, it will directly return to the original Statement, Otherwise, build a RunBefores Object returns , Obviously this RunBefores It's also Statement Subclasses of .

So let's look at this RunBefores How are classes implemented to ensure that they have BeforeClass The method of annotation precedes Test The annotated method performs .

Be careful evaluate Method , First, traverse and execute BeforeClass Method of annotation , Then the test method is executed Statement Object's evaluate Method .
Obvious , The decoration mode is used here .

Decoration mode : Dynamically add some additional responsibilities to an object

in other words ,JUnit Add additional responsibilities to the test method dynamically through decoration mode . I believe that you can probably know how to implement other methods without looking at them ?

RunNotifier The role of

RunNotifier The implementation of is more obvious : Observer mode !

Observer mode : Defines a one-to-many dependency between objects , When the state of an object changes , All objects that depend on it are notified and automatically updated .

The function of using observer mode is to decouple the execution of test and the display of test results . Let's take a look JUnit How to do it .

Let's move on to the top childrenInvoker Way to look down ,childrenInvoker Built a Statement, What's actually called is ParentRunner Inside runChildren Method .

Why use threads to execute here ? The reason is simple , Here are the test methods performed one by one , There is no relationship between each test method , So threads are used here , It can improve the efficiency of test execution .

Be careful : Although each test method is independent , But the results Listener It's public. , Then here comes the issue of competition .JUnit How to ensure thread safety ? Here is left for you to find the answer in the source code . Wake up , Let's get to know CopyOnWriteArrayList

The thread method executes runChild Method , And this is an abstract method , Implemented by subclasses . It has two implementation classes , One is BlockJUnit4ClassRunner class , One is Suite class , Obviously Suite It is used to execute a batch of test methods . This relationship is the routine of combination mode !Suite and BlockJunit4ClassRunner It must be a combination mode . If you don't believe it, you can verify it yourself , No more combing here .

Let's look directly here BlockJUnit4ClassRunner Of runChild Method .

Pay attention to the methodBlock Method , Think back to the above classBlock Method , Can you guess the logic here ?
The last to runLeaf Method , That is, the method of final execution , Let's see notifier What specific work has been done .

This is equivalent to statement Start of execution 、 The end and error reporting phases are monitored , Different methods are called . And these methods , Finally entrusted to register to TestNotifier Of TestListener 了 . such as addFailure Method , The final call is TestListener Of testFailure Method .

Now let's just take a look at which classes inherit TestListener class , We can know how to deal with the test results .( The whole execution process will be connected in series from here
We use the simplest TextListener For example .

You can see , This is simply output to the command line .
If you want other ways to deal with the results , You just need to write a class to implement TestListener The class can .
Let's turn it around , If you don't use listener mode , Whether the test execution and result processing are coupled together , And there is no scalability ?

summary

In this paper , In order to answer the questions raised above , We sort out the core code flow at the code level , And understand why it's designed like this , If not, what problems will it bring .

meanwhile , You should also realize , Being familiar with design patterns can greatly improve the efficiency of reading source code . Suppose you don't know design patterns , You need to sort out the relationship between classes first , Like the one above RunNotifier and TestListener The relationship between , Then think about why you design like this , At the same time, you can find information on the Internet to confirm the relationship between the two , In the mode of learning observer . The next time you see a similar structure , Can quickly sort out logic .

Below we will combine Spring To sort it out JUnit Of Runner, Complete the last puzzle of the whole test process .

版权声明
本文为[One Yu and one Yu]所创,转载请带上原文链接,感谢
https://cdmana.com/2022/134/202205141227435550.html

Scroll to Top