使用可插拔架构

您可能会遇到您的契约以其他格式定义的情况,例如 YAML、RAML 或 PACT。在这些情况下,您仍然希望受益于自动生成测试和存根。您可以添加自己的实现来生成测试和存根。此外,您可以自定义测试的生成方式(例如,您可以为其他语言生成测试)和存根的生成方式(例如,您可以为其他 HTTP 服务器实现生成存根)。

自定义契约转换器

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

使用自定义存根生成器

如果您想为 WireMock 以外的存根服务器生成存根,您可以插入自己的 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 存根生成。

您可以提供多个存根生成器实现。例如,从单个 DSL 中,您可以生成 WireMock 存根和 Pact 文件。

使用自定义存根运行器

如果您决定使用自定义存根生成,您还需要一种自定义方式来运行不同存根提供程序的存根。

假设您使用 Moco 构建存根,并且您编写了一个存根生成器并将存根放在 JAR 文件中。

为了让 Stub Runner 知道如何运行您的存根,您必须定义一个自定义 HTTP 存根服务器实现,它可能类似于以下示例

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 运行存根了。

如果您不提供任何实现,则使用默认(WireMock)实现。如果您提供多个,则使用列表中的第一个。

使用自定义存根下载器

您可以通过创建 StubDownloaderBuilder 接口的实现来自定义存根的下载方式,如以下示例所示

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

现在您可以选择包含存根源的文件夹。

如果您不提供任何实现,则使用默认(扫描类路径)实现。如果您提供 stubsMode = StubRunnerProperties.StubsMode.LOCALstubsMode = StubRunnerProperties.StubsMode.REMOTE,则使用 Aether 实现。如果您提供多个,则使用列表中的第一个。

使用 SCM 存根下载器

只要 repositoryRoot 以 SCM 协议开头(目前,我们只支持 git://),存根下载器就会尝试克隆存储库并将其用作生成测试或存根的契约源。

通过环境变量、系统属性或插件或契约存储库配置中设置的属性,您可以调整下载器的行为。下表描述了可用的属性

表 1. SCM 存根下载器属性

属性类型

属性名称

描述

* git.branch(插件属性)

* spring.cloud.contract.stubrunner.properties.git.branch(系统属性)

* SPRING_CLOUD_CONTRACT_STUBRUNNER_PROPERTIES_GIT_BRANCH(环境变量)

master

要检出的分支

* git.username(插件属性)

* spring.cloud.contract.stubrunner.properties.git.username(系统属性)

* SPRING_CLOUD_CONTRACT_STUBRUNNER_PROPERTIES_GIT_USERNAME(环境变量)

Git 克隆用户名

* git.password(插件属性)

* spring.cloud.contract.stubrunner.properties.git.password(系统属性)

* SPRING_CLOUD_CONTRACT_STUBRUNNER_PROPERTIES_GIT_PASSWORD(环境变量)

Git 克隆密码

* git.no-of-attempts(插件属性)

* spring.cloud.contract.stubrunner.properties.git.no-of-attempts(系统属性)

* SPRING_CLOUD_CONTRACT_STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS(环境变量)

10

尝试将提交推送到 origin 的次数

* git.wait-between-attempts(插件属性)

* spring.cloud.contract.stubrunner.properties.git.wait-between-attempts(系统属性)

* SPRING_CLOUD_CONTRACT_STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS(环境变量)

1000

尝试将提交推送到 origin 之间等待的毫秒数

© . This site is unofficial and not affiliated with VMware.