使用可插拔架構
您可能會遇到合約以其他格式定義的情況,例如 YAML、RAML 或 PACT。在這些情況下,您仍然希望受益於自動生成測試和 stubs。您可以添加自己的實作來生成測試和 stubs。此外,您可以自訂測試的生成方式(例如,您可以為其他語言生成測試)以及 stubs 的生成方式(例如,您可以為其他 HTTP 伺服器實作生成 stubs)。
自訂合約轉換器
`ContractConverter` 介面讓您可以註冊自己的合約結構轉換器實作。以下程式碼清單顯示了 `ContractConverter` 介面
import java.io.File;
import java.util.Collection;
/**
* Converter to be used to convert FROM {@link File} TO {@link Contract} and from
* {@link Contract} to {@code T}.
*
* @param <T> - type to which we want to convert the contract
* @author Marcin Grzejszczak
* @since 1.1.0
*/
public interface ContractConverter<T> extends ContractStorer<T>, ContractReader<T> {
/**
* Should this file be accepted by the converter. Can use the file extension to check
* if the conversion is possible.
* @param file - file to be considered for conversion
* @return - {@code true} if the given implementation can convert the file
*/
boolean isAccepted(File file);
/**
* Converts the given {@link File} to its {@link Contract} representation.
* @param file - file to convert
* @return - {@link Contract} representation of the file
*/
Collection<Contract> convertFrom(File file);
/**
* Converts the given {@link Contract} to a {@link T} representation.
* @param contract - the parsed contract
* @return - {@link T} the type to which we do the conversion
*/
T convertTo(Collection<Contract> contract);
}
您的實作必須定義啟動轉換的條件。此外,您必須定義如何在兩個方向執行轉換。
一旦您創建了實作,您必須創建一個 `/META-INF/spring.factories` 檔案,在其中提供您的實作的完整限定名稱。 |
以下範例顯示了一個典型的 `spring.factories` 檔案
org.springframework.cloud.contract.spec.ContractConverter=\
org.springframework.cloud.contract.verifier.converter.YamlContractConverter
使用自訂測試生成器
如果您想為 Java 以外的語言生成測試,或者您對驗證器建構 Java 測試的方式不滿意,您可以註冊自己的實作。
`SingleTestGenerator` 介面讓您可以註冊自己的實作。以下程式碼清單顯示了 `SingleTestGenerator` 介面
import java.nio.file.Path;
import java.util.Collection;
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
import org.springframework.cloud.contract.verifier.file.ContractMetadata;
/**
* Builds a single test.
*
* @since 1.1.0
*/
public interface SingleTestGenerator {
/**
* Creates contents of a single test class in which all test scenarios from the
* contract metadata should be placed.
* @param properties - properties passed to the plugin
* @param listOfFiles - list of parsed contracts with additional metadata
* @param generatedClassData - information about the generated class
* @param includedDirectoryRelativePath - relative path to the included directory
* @return contents of a single test class
*/
String buildClass(ContractVerifierConfigProperties properties, Collection<ContractMetadata> listOfFiles,
String includedDirectoryRelativePath, GeneratedClassData generatedClassData);
class GeneratedClassData {
public final String className;
public final String classPackage;
public final Path testClassPath;
public GeneratedClassData(String className, String classPackage, Path testClassPath) {
this.className = className;
this.classPackage = classPackage;
this.testClassPath = testClassPath;
}
}
}
同樣,您必須提供一個 `spring.factories` 檔案,例如以下範例中顯示的檔案
org.springframework.cloud.contract.verifier.builder.SingleTestGenerator=/
com.example.MyGenerator
使用自訂 Stub 生成器
如果您想為 WireMock 以外的 stub 伺服器生成 stubs,您可以插入自己的 `StubGenerator` 介面實作。以下程式碼清單顯示了 `StubGenerator` 介面
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.verifier.file.ContractMetadata;
/**
* Converts contracts into their stub representation.
*
* @param <T> - type of stub mapping
* @since 1.1.0
*/
public interface StubGenerator<T> {
/**
* @param mapping - potential stub mapping mapping
* @return {@code true} if this converter could have generated this mapping stub.
*/
default boolean canReadStubMapping(File mapping) {
return mapping.getName().endsWith(fileExtension());
}
/**
* @param rootName - root name of the contract
* @param content - metadata of the contract
* @return the collection of converted contracts into stubs. One contract can result
* in multiple stubs.
*/
Map<Contract, String> convertContents(String rootName, ContractMetadata content);
/**
* Post process a generated stub mapping.
* @param stubMapping - mapping of a stub
* @param contract - contract for which stub was generated
* @return the converted stub mapping
*/
default T postProcessStubMapping(T stubMapping, Contract contract) {
List<StubPostProcessor> processors = StubPostProcessor.PROCESSORS.stream().filter(p -> p.isApplicable(contract))
.collect(Collectors.toList());
if (processors.isEmpty()) {
return defaultStubMappingPostProcessing(stubMapping, contract);
}
T stub = stubMapping;
for (StubPostProcessor processor : processors) {
stub = (T) processor.postProcess(stub, contract);
}
return stub;
}
/**
* Stub mapping to chose when no post processors where found on the classpath.
* @param stubMapping - mapping of a stub
* @param contract - contract for which stub was generated
* @return the converted stub mapping
*/
default T defaultStubMappingPostProcessing(T stubMapping, Contract contract) {
return stubMapping;
}
/**
* @param inputFileName - name of the input file
* @return the name of the converted stub file. If you have multiple contracts in a
* single file then a prefix will be added to the generated file. If you provide the
* {@link Contract#getName} field then that field will override the generated file
* name.
*
* Example: name of file with 2 contracts is {@code foo.groovy}, it will be converted
* by the implementation to {@code foo.json}. The recursive file converter will create
* two files {@code 0_foo.json} and {@code 1_foo.json}
*/
String generateOutputFileNameForInput(String inputFileName);
/**
* Describes the file extension of the generated mapping that this stub generator can
* handle.
* @return string describing the file extension
*/
default String fileExtension() {
return ".json";
}
}
同樣,您必須提供一個 `spring.factories` 檔案,例如以下範例中顯示的檔案
# Stub converters
org.springframework.cloud.contract.verifier.converter.StubGenerator=\
org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter
預設實作是 WireMock stub 生成。
您可以提供多個 stub 生成器實作。例如,從單個 DSL,您可以同時生成 WireMock stubs 和 Pact 檔案。 |
使用自訂 Stub Runner
如果您決定使用自訂 stub 生成,您還需要一種自訂方式來使用不同的 stub 提供者運行 stubs。
假設您使用 Moco 來建構您的 stubs,並且您已經編寫了一個 stub 生成器並將您的 stubs 放在 JAR 檔案中。
為了讓 Stub Runner 知道如何運行您的 stubs,您必須定義一個自訂 HTTP Stub 伺服器實作,這可能類似於以下範例
import com.github.dreamhead.moco.bootstrap.arg.HttpArgs
import com.github.dreamhead.moco.runner.JsonRunner
import com.github.dreamhead.moco.runner.RunnerSetting
import groovy.transform.CompileStatic
import groovy.util.logging.Commons
import org.springframework.cloud.contract.stubrunner.HttpServerStub
import org.springframework.cloud.contract.stubrunner.HttpServerStubConfiguration
@Commons
@CompileStatic
class MocoHttpServerStub implements HttpServerStub {
private boolean started
private JsonRunner runner
private int port
@Override
int port() {
if (!isRunning()) {
return -1
}
return port
}
@Override
boolean isRunning() {
return started
}
@Override
HttpServerStub start(HttpServerStubConfiguration configuration) {
this.port = configuration.port
return this
}
@Override
HttpServerStub stop() {
if (!isRunning()) {
return this
}
this.runner.stop()
return this
}
@Override
HttpServerStub registerMappings(Collection<File> stubFiles) {
List<RunnerSetting> settings = stubFiles.findAll { it.name.endsWith("json") }
.collect {
log.info("Trying to parse [${it.name}]")
try {
return RunnerSetting.aRunnerSetting().addStream(it.newInputStream()).
build()
}
catch (Exception e) {
log.warn("Exception occurred while trying to parse file [${it.name}]", e)
return null
}
}.findAll { it }
this.runner = JsonRunner.newJsonRunnerWithSetting(settings,
HttpArgs.httpArgs().withPort(this.port).build())
this.runner.run()
this.started = true
return this
}
@Override
String registeredMappings() {
return ""
}
@Override
boolean isAccepted(File file) {
return file.name.endsWith(".json")
}
}
然後您可以將其註冊到您的 `spring.factories` 檔案中,如下例所示
org.springframework.cloud.contract.stubrunner.HttpServerStub=\
org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub
現在您可以使用 Moco 運行 stubs 了。
如果您不提供任何實作,則使用預設 (WireMock) 實作。如果您提供多個實作,則使用列表中的第一個實作。 |
使用自訂 Stub 下載器
您可以通過創建 `StubDownloaderBuilder` 介面的實作來自訂 stubs 的下載方式,如下例所示
class CustomStubDownloaderBuilder implements StubDownloaderBuilder {
@Override
public StubDownloader build(final StubRunnerOptions stubRunnerOptions) {
return new StubDownloader() {
@Override
public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
StubConfiguration config) {
File unpackedStubs = retrieveStubs();
return new AbstractMap.SimpleEntry<>(
new StubConfiguration(config.getGroupId(), config.getArtifactId(), version,
config.getClassifier()), unpackedStubs);
}
File retrieveStubs() {
// here goes your custom logic to provide a folder where all the stubs reside
}
}
}
}
然後您可以將其註冊到您的 `spring.factories` 檔案中,如下例所示
# Example of a custom Stub Downloader Provider
org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\
com.example.CustomStubDownloaderBuilder
現在您可以選擇一個包含 stubs 來源的資料夾。
如果您不提供任何實作,則使用預設實作(掃描類別路徑)。如果您提供 `stubsMode = StubRunnerProperties.StubsMode.LOCAL` 或 `stubsMode = StubRunnerProperties.StubsMode.REMOTE`,則使用 Aether 實作。如果您提供多個實作,則使用列表中的第一個實作。 |
使用 SCM Stub 下載器
每當 `repositoryRoot` 以 SCM 協定開頭(目前,我們僅支援 `git://`)時,stub 下載器會嘗試克隆儲存庫並將其用作合約來源以生成測試或 stubs。
通過環境變數、系統屬性或外掛程式或合約儲存庫配置中設置的屬性,您可以調整下載器的行為。下表描述了可用的屬性
屬性類型 |
屬性名稱 |
描述 |
* * * |
master |
要檢出的分支 |
* * * |
Git 克隆使用者名稱 |
|
* * * |
Git 克隆密碼 |
|
* * * |
10 |
將提交推送到 `origin` 的嘗試次數 |
* * * |
1000 |
在嘗試將提交推送到 `origin` 之間等待的毫秒數 |