JUnit5的啓動 - Launcher

Launcher概述

Launcher是JUnit5的啓動類,也是對啓動進行擴展的主要入口,擴展通過實現自定義的TestEngine來自定義測試類的發現和執行邏輯以達到定製化測試的目的

Launcher啓動示例代碼

public static void main(String[] args) {
        //設置搜索和過濾規則
        LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
                .selectors(
                        selectPackage("zj"),
                        selectClass(*MyTest.class)
                )
                .filters(
                        includeClassNamePatterns(".*Tests")
                )
                .build();

        Launcher launcher = LauncherFactory.create();
        // Register a listener of your choice
        //通過監聽器來監聽獲取執行結果
        TestExecutionListener listener = new SummaryGeneratingListener();
        launcher.registerTestExecutionListeners(listener);
        launcher.execute(request);
    }

啓動分爲如下幾步:

1. 構造LauncherDiscoveryRequest指定測試類的查找和過濾規則

2. 通過 LauncherFactory.create() 來獲取Launcher默認實現

3. 通過添加TestExecutionListener來進行測試結果的監聽

4. 執行launcher.execute(req)方法啓動測試

簡單來說就是掃描註定的文件夾或找到指定類名的類,經過Filter過濾後交給TestEngine去執行

啓動過程中涉及的類

DiscoverySelector

負責定義哪些資源可以被TestEngine用來尋找測試類,比如指定java類名或者指定目錄路徑

Filter

作爲TestEngine的過濾器,過濾DiscoverySelector發現的資源

LauncherDiscoveryRequest

使用不同的Filter定義了Launcher和TestEngine以及測試類的過濾規則,使用DiscoverySelector發現測試類

TestEngine

作爲發現和執行符合LauncherDiscoveryRequest規則的測試的主要組件,對不同的編程模型可以實現自己的TestEngine,比如JupiterTestEngine

TestEngine在創建Launcher的時候通過JAVA SPI發現具體實現,JUnit5中有兩種實現 :JupiterTestEngine 和 VintageTestEngine

TestExecutionListener

定義了測試執行期間發生的事件類型,註冊到Launcher的實現類將會在事件發生時得到通知


DefaultLauncher

做爲Launcher的默認實現,持有TestEngine和TestExecutionListener集合,它們通過JAVA SPI發現,負責執行TestEngine來啓動測試

各步驟詳解

1. 構造LauncherDiscoveryRequest指定測試類的查找和過濾規則

	public LauncherDiscoveryRequest build() {
		LauncherConfigurationParameters launcherConfigurationParameters = new LauncherConfigurationParameters(
			this.configurationParameters);
		return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters,
			this.postDiscoveryFilters, launcherConfigurationParameters);
	}


2.利用LauncherFactory創建DefaultLauncher

	public static Launcher create() throws PreconditionViolationException {
		Launcher launcher = new DefaultLauncher(new ServiceLoaderTestEngineRegistry().loadTestEngines());
		for (TestExecutionListener listener : new ServiceLoaderTestExecutionListenerRegistry().loadListeners()) {
			launcher.registerTestExecutionListeners(listener);
		}
		return launcher;
	}
這裏利用JAVA SPI來發現TestEngine和TestExecutionListener的實現集合,保存在Launcher中

3. 通過添加TestExecutionListener來進行測試結果的監聽

        TestExecutionListener listener = new SummaryGeneratingListener();
        launcher.registerTestExecutionListeners(listener);


4. 執行launcher.execute(req)方法啓動測試

public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) {
		Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null");
		Preconditions.notNull(listeners, "TestExecutionListener array must not be null");
		Preconditions.containsNoNullElements(listeners, "individual listeners must not be null");
		execute(discoverRoot(discoveryRequest, "execution"), discoveryRequest.getConfigurationParameters(), listeners);
	}
4.1 調用discoveryRoot()來執行TestEngine的過濾器,過濾的結果通過類Root返回,Root持有TestEngine的map集合

	private Root discoverRoot(LauncherDiscoveryRequest discoveryRequest, String phase) {
		Root root = new Root();

		for (TestEngine testEngine : this.testEngines) {
			// @formatter:off
			boolean engineIsExcluded = discoveryRequest.getEngineFilters().stream()
					.map(engineFilter -> engineFilter.apply(testEngine))
					.anyMatch(FilterResult::excluded);
			// @formatter:on

			if (engineIsExcluded) {
				logger.debug(() -> String.format(
					"Test discovery for engine '%s' was skipped due to an EngineFilter in phase '%s'.",
					testEngine.getId(), phase));
				continue;
			}

			logger.debug(() -> String.format("Discovering tests during Launcher %s phase in engine '%s'.", phase,
				testEngine.getId()));

			Optional<TestDescriptor> engineRoot = discoverEngineRoot(testEngine, discoveryRequest);
			engineRoot.ifPresent(rootDescriptor -> root.add(testEngine, rootDescriptor));
		}
		root.applyPostDiscoveryFilters(discoveryRequest);
		root.prune();
		return root;
	}

4.2 繼續調用execute(Root ...)

	private void execute(Root root, ConfigurationParameters configurationParameters,
			TestExecutionListener... listeners) {

		TestExecutionListenerRegistry listenerRegistry = buildListenerRegistryForExecution(listeners);
		TestPlan testPlan = TestPlan.from(root.getEngineDescriptors());
		TestExecutionListener testExecutionListener = listenerRegistry.getCompositeTestExecutionListener();
		testExecutionListener.testPlanExecutionStarted(testPlan);
		ExecutionListenerAdapter engineExecutionListener = new ExecutionListenerAdapter(testPlan,
			testExecutionListener);
		for (TestEngine testEngine : root.getTestEngines()) {
			TestDescriptor testDescriptor = root.getTestDescriptorFor(testEngine);
			execute(testEngine, new ExecutionRequest(testDescriptor, engineExecutionListener, configurationParameters));
		}
		testExecutionListener.testPlanExecutionFinished(testPlan);
	}

主要邏輯就是構造監聽器、TestDescriptor和ExecutionRequest的構造以及TestEngine的執行




Launcher構造和啓動流程圖











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章