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 ？
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]所创，转载请带上原文链接，感谢