从PostgreSQL下载一个相对较大的二进制数据文件的问题。在存储和获取这些数据方面有几个限制(所有的限制都可以在官方文档中找到)。为了解决这个问题,有人建议找到更合适的数据存储。由于一些内部原因,众所周知的Amazon S3桶被选为这个目的。这个选择影响了项目的单元测试基础。仍然不可能继续使用轻量级的数据库,如HSQL或H2来实现测试。这是一个关键问题,我们将在这篇文章中尝试解决.
对象存储构建
保持集成测试活力的一个可能的解决方案是实现一些模拟的对象存储,与S3 bucket客户端完全兼容,另一方面,我们可以使用已经存在的这种类型的对象存储。MinIO是一个很好的例子,它相当简单,但高性能的对象存储,同时与Amazon S3兼容(至少在文档中是这样写的)。
为了将MinIO集成到我们的集成测试中,我们将使用一个用Java编写的强大的Testcontainers库。Testcontainers是一个特殊的库,它支持JUnit测试,并提供轻量级的、可抛弃的普通数据库、Selenium网络浏览器和其他任何可以在Docker容器中运行的实例。要开始使用这个神奇的库,只需要有Docker并在我们的pom.xml中添加以下依赖:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.12.3</version>
<scope>test</scope>
</dependency>
不幸的是,我们的目标没有合适的容器,但该库提供了所有必要的工具,可以自己轻松地创建它。幸运的是,在DockerHub上有一个MinIO的官方docker镜像。为了创建一个自己的MinIO容器,有必要用自定义数据扩展GenericContainer。DEFAULT_PORT(MonIO文档建议使用9000端口),DEFAULT_IMAGE(镜像名称),DEFAULT_TAG(镜像版本)。 要注意标签的分配! 在我们的例子中,使用了 "edge "标签来支持最后部署的MinIO版本,但在大多数情况下,最好固定标签并不时地手动更新,以避免不可预知的测试崩溃。我们也强烈建议提供凭证(访问密钥、秘密密钥)来控制对容器的访问。下面是一个实现自定义MinIO容器的例子:
public class MinioContainer extends GenericContainer<MinioContainer> {
private static final int DEFAULT_PORT = 9000;
private static final String DEFAULT_IMAGE = "minio/minio";
private static final String DEFAULT_TAG = "edge";
private static final String MINIO_ACCESS_KEY = "MINIO_ACCESS_KEY";
private static final String MINIO_SECRET_KEY = "MINIO_SECRET_KEY";
private static final String DEFAULT_STORAGE_DIRECTORY = "/data";
private static final String HEALTH_ENDPOINT = "/minio/health/ready";
public MinioContainer(CredentialsProvider credentials) {
this(DEFAULT_IMAGE + ":" + DEFAULT_TAG, credentials);
}
public MinioContainer(String image, CredentialsProvider credentials) {
super(image == null ? DEFAULT_IMAGE + ":" + DEFAULT_TAG : image);
withNetworkAliases("minio-" + Base58.randomString(6));
addExposedPort(DEFAULT_PORT);
if (credentials != null) {
withEnv(MINIO_ACCESS_KEY, credentials.getAccessKey());
withEnv(MINIO_SECRET_KEY, credentials.getSecretKey());
}
withCommand("server", DEFAULT_STORAGE_DIRECTORY);
setWaitStrategy(new HttpWaitStrategy()
.forPort(DEFAULT_PORT)
.forPath(HEALTH_ENDPOINT)
.withStartupTimeout(Duration.ofMinutes(2)));
}
public String getHostAddress() {
return getContainerIpAddress() + ":" + getMappedPort(DEFAULT_PORT);
}
public static class CredentialsProvider {
private String accessKey;
private String secretKey;
public CredentialsProvider(String accessKey, String secretKey) {
this.accessKey = accessKey;
this.secretKey = secretKey;
}
public String getAccessKey() {
return accessKey;
}
public String getSecretKey() {
return secretKey;
}
}
}测试
由于我们有一个合适的测试容器,可以作为Amazon S3桶对象存储的提供者,现在是时候用它来展示一个简单的JUnit测试的例子。当然,首先我们需要配置S3客户端来与我们的容器进行交互。在我们的案例中,我们使用原始的AmazonS3Client。因此,为了实现我们的单元测试,我们需要添加一个额外的依赖:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.60</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.11.60</version>
</dependency>
这里是一个普通的测试,用于创建一个具有指定名称的S3桶:
public class MinioContainerTest {
private static final String ACCESS_KEY = "accessKey";
private static final String SECRET_KEY = "secretKey";
private static final String BUCKET = "bucket";
private AmazonS3Client client = null;
@After
public void shutDown() {
if (client != null) {
client.shutdown();
client = null;
}
}
@Test
public void testCreateBucket() {
try (MinioContainer container = new MinioContainer(
new MinioContainer.CredentialsProvider(ACCESS_KEY, SECRET_KEY))) {
container.start();
client = getClient(container);
Bucket bucket = client.createBucket(BUCKET);
assertNotNull(bucket);
assertEquals(BUCKET, bucket.getName());
List<Bucket> buckets = client.listBuckets();
assertNotNull(buckets);
assertEquals(1, buckets.size());
assertTrue(buckets.stream()
.map(Bucket::getName)
.collect(Collectors.toList())
.contains(BUCKET));
}
}
private AmazonS3Client getClient(MinioContainer container) {
S3ClientOptions clientOptions = S3ClientOptions
.builder()
.setPathStyleAccess(true)
.build();
client = new AmazonS3Client(new AWSStaticCredentialsProvider(
new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY)));
client.setEndpoint("http://" + container.getHostAddress());
client.setS3ClientOptions(clientOptions);
return client;
}
}结论
以上仅适用于基于Junit的集成测试场景,自动化部署Mino用于集成测试依赖。单元测试场景我们更多推荐是隔离外部依赖。
如有想了解更多软件设计与架构, 系统IT,企业信息化, 团队管理 资讯,请关注我的微信订阅号:
作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
该文章也同时发布在我的独立博客中-Petter Liu Blog。
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象