跳到主要内容
NoSQLUnit 核心功能与使用指南 | 极客日志
Java java
NoSQLUnit 核心功能与使用指南 NoSQLUnit 是基于 JUnit 的扩展库,旨在简化 NoSQL 数据库的单元测试与集成测试。它通过生命周期管理规则和状态重置机制,解决了 NoSQL 异构性带来的测试难题。支持 MongoDB、Neo4j、Cassandra 等多种引擎,提供内存、托管及远程三种部署模式,并允许自定义数据集格式与验证策略,是 Java 后端测试 NoSQL 组件的理想工具。
概述
单元测试是验证应用程序中最小可测试部分的方法。单元测试必须遵循 FIRST 原则:快速(Fast)、独立(Isolated)、可重复(Repeatable)、自我验证(Self-Validated)和及时(Timely)。
在讨论 JEE 应用时,如果没有持久层(通常是关系型数据库或 NoSQL 数据库),那是不完整的。因此,编写持久层的单元测试同样重要。在进行持久层单元测试时,我们需要重点关注不破坏 FIRST 原则中的两个核心概念:速度和独立性。
我们的测试必须是快速 的,这意味着它们不应访问网络或文件系统。对于关系型数据库(RDBMS),有许多 Java 内存数据库可用,如 Apache Derby、H2 或 HSQLDB。这些数据库嵌入到程序中,数据存储在内存中,因此测试速度很快。问题在于 NoSQL 系统,因为它们具有异构性。有些系统使用文档方法(如 MongoDB),有些使用列存储(如 HBase),还有些使用图结构(如 Neo4j)。因此,内存模式通常由供应商提供,没有通用的解决方案。
我们的测试必须是独立 的。一个测试方法修改另一个测试方法的结果是不可接受的。在持久层测试中,这种情况发生在之前的测试方法向数据库插入条目,而下一个测试方法执行时发现该更改。因此,在每个测试执行之前,数据库应处于已知状态。如果测试发现数据库处于已知状态,测试就是可重复的;如果测试断言依赖于前一个测试的执行,每次执行将是唯一的。对于同质系统如 RDBMS,DBUnit 可用于在每次执行前维护数据库状态。但对于异构的 NoSQL 系统,没有像 DBUnit 这样的框架。
NoSQLUnit 通过提供一个 JUnit 扩展来解决这个问题,帮助我们管理 NoSQL 系统的生命周期,并确保数据库保持在已知状态。
环境要求
运行 NoSQLUnit 需要提供 JUnit 4.10 或更高版本。这是因为 NoSQLUnit 使用了 Rules ,它们在 4.10 版本中发生了变化。
虽然它应该能在 JDK 5 上工作,但 jar 包是使用 JDK 6 编译的。
核心概念
NoSQLUnit 是一个 JUnit 扩展,旨在简化使用 NoSQL 后端的系统和集成测试的编写。它由两组 Rules 和一组注解组成。
第一组 Rules 负责管理数据库生命周期;每个支持的后端都有两个规则。
第一个是 in-memory 模式(如果可能)。此模式负责以'内存'模式启动和停止数据库系统。这通常用于单元测试执行期间。
第二个是 managed 模式。此模式负责启动 NoSQL 服务器作为远程进程(在本地机器上)并停止它。这通常用于集成测试执行期间。
你可以将它们添加到测试套件和/或测试类中,NoSQLUnit 会确保只启动一次数据库。
第二组 Rules 负责将数据库保持在已知状态。每个支持的后端都有自己的实现,可以理解为连接到定义数据库的连接,用于执行维持系统稳定性所需的操作。
由于 NoSQL 数据库是异构的,每个系统都需要自己的实现。
最后提供了两个注解:@UsingDataSet 和 @ShouldMatchDataSet(感谢 Arquillian 团队提供的命名)。
初始化数据库 (Seeding Database)
@UsingDataSet 用于用定义的数据集初始化数据库。简而言之,数据集是包含要插入到配置数据库的所有数据的文件。为了初始化数据库,请使用 @UsingDataSet 注解,你可以在测试本身或类级别定义它。如果两者都有定义,测试级别的注解优先。
该注解有两个属性:locations 和 loadStrategy。
使用 locations 属性,你可以指定 classpath 数据集的位置。位置相对于测试类的位置。注意,可以指定多个数据集。
此外,可以使用 withSelectiveLocations 属性来指定数据集位置。详见高级用法章节。
如果未显式指定文件,则应用以下策略:
首先在测试类的同一包中的 classpath 上搜索文件,文件名格式为 [test class name]#[test method name].[format](仅当注解存在于测试方法时)。
如果第一条规则不满足或注解定义在类作用域中,则在测试类的同一包中的 classpath 上搜索下一个文件,格式为 [test class name].[default format]。
注意
数据集必须位于 classpath 中,且格式取决于 NoSQL 供应商。
INSERT : 在执行任何测试方法之前插入定义的数据集。
DELETE_ALL : 在执行任何测试方法之前删除数据库中的所有元素。
CLEAN_INSERT : 最常用的策略。在执行任何测试方法之前,先删除数据库中的所有元素,然后插入定义的数据集。
@UsingDataSet(locations="my_data_set.json", loadStrategy=LoadStrategyEnum.INSERT)
验证数据库 (Verifying Database) 有时直接在测试代码中断言数据库状态可能涉及大量工作。通过在测试方法上使用 @ShouldMatchDataSet ,NoSQLUnit 将在测试执行后检查数据库是否包含预期的条目。与 @ShouldMatchDataSet 注解一样,你可以定义 classpath 文件位置,或者使用 withSelectiveMatcher,详见高级用法章节。如果未提供数据集,则使用以下约定:
首先在测试类的同一包中的 classpath 上搜索文件,文件名格式为 [test class name]#[test method name]-expected.[format](仅当注解存在于测试方法时)。
如果第一条规则不满足或注解定义在类作用域中,则在测试类的同一包中的 classpath 上搜索文件,格式为 [test class name]-expected.[default format]。
注意
数据集必须位于 classpath 中,且格式取决于 NoSQL 供应商。
@ShouldMatchDataSet(location="my_expected_data_set.json")
MongoDB 引擎 MongoDB 是一种 NoSQL 数据库,它以动态模式的 JSON-like 文档形式存储结构化数据。
NoSQLUnit 通过以下类支持 MongoDB :
In Memory : com.lordofthejars.nosqlunit.mongodb.InMemoryMongoDb
Managed : com.lordofthejars.nosqlunit.mongodb.ManagedMongoDb
NoSQLUnit Management : com.lordofthejars.nosqlunit.mongodb.MongoDbRule
Maven 设置 要在 MongoDB 中使用 NoSQLUnit ,只需添加以下依赖:
<dependency >
<groupId > com.lordofthejars</groupId >
<artifactId > nosqlunit-mongodb</artifactId >
<version > ${version.nosqlunit}</version >
</dependency >
注意,如果你计划使用 in-memory 方法,它是使用 Fongo 实现的。Fongo 是一个新项目,有助于基于 Java 的 MongoDb 应用的单元测试。
数据集格式 MongoDB 模块中的默认数据集文件格式是 json 。
{
"name_collection1" : [
{
"attribute_1" : "value1" ,
"attribute_2" : "value2"
} ,
{
"attribute_3" : 2 ,
"attribute_4" : "value4"
}
] ,
"name_collection2" : [ ...]
}
提示
如果你想使用 ISODate 函数或其他 JavaScript 函数,请查看 MongoDB Java Driver 如何处理。例如对于 ISODate:
"bornAt" : { " $date" : "2011-01-05T10:09:15.210Z" }
使用最新版本的 MongoDB ,还支持索引,允许开发者通过定义的文档属性定义索引。在这种情况下,数据集已更改以让我们定义索引。
{
"collection1" : {
"indexes" : [
{
"index" : {
"code" : 1
}
}
] ,
"data" : [
{
"id" : 1 ,
"code" : "JSON dataset"
} ,
{
"id" : 2 ,
"code" : "Another row"
}
]
}
}
注意我们定义了集合名称,然后定义了两个子文档。第一个是我们定义索引数组的地方,所有这些都与定义的集合相关,我们定义哪些字段将被索引(与 MongoDB 索引规范中定义的文档相同)。然后是 data 属性,其中定义要放入待测集合下的所有文档。
入门指南
生命周期管理策略 第一步是确定你的测试需要哪种生命周期管理策略。根据你正在实施的测试类型(单元测试、集成测试、部署测试等),你可能需要 in-memory 方法、managed 方法或 remote 方法。
要配置 in-memory 方法,你只需要实例化以下内容:
import static com.lordofthejars.nosqlunit.mongodb.InMemoryMongoDb.InMemoryMongoRuleBuilder.newInMemoryMongoDbRule;
@ClassRule
public static InMemoryMongoDb inMemoryMongoDb = newInMemoryMongoDbRule().build();
要配置 managed 方式,你应该使用 ManagedMongoDb 规则,可能需要一些参数。
import static com.lordofthejars.nosqlunit.mongodb.ManagedMongoDb.MongoServerRuleBuilder.newManagedMongoDbRule;
@ClassRule
public static ManagedMongoDb managedMongoDb = newManagedMongoDbRule().build();
默认情况下,managed MongoDB 规则使用以下默认值:
MongoDB 安装目录从 MONGO_HOME 系统环境变量中检索。
目标路径,即启动 MongoDb 服务器的目录,是 target/mongo-temp。
数据库路径位于 {target path} /mongo-dbpath。
在 Windows 系统中,可执行文件应为 bin/mongod.exe,而在 MAC OS 和 *nix 中应为 bin/mongod。
无日志记录。
ManagedMongoDb 可以从头创建,但为了简化生活,提供了一个使用 MongoServerRuleBuilder 类的 DSL 。
import static com.lordofthejars.nosqlunit.mongodb.ManagedMongoDb.MongoServerRuleBuilder.newManagedMongoDbRule;
@ClassRule
public static ManagedMongoDb managedMongoDb = newManagedMongoDbRule()
.mongodPath("/opt/mongo" )
.appendSingleCommandLineArguments("-vvv" )
.build();
在这里,我们覆盖了 MONGO_HOME 变量(如果已设置),并将 mongo home 设置为 /opt/mongo。此外,我们正在向 MongoDB 可执行文件附加单个参数,在此情况下将日志级别设置为数字 3 (-vvv)。你也可以使用 appendCommandLineArguments(String argumentName, String argumentValue) 方法附加 property=value 参数。
警告
当你指定命令行参数时,请记住在必要时添加斜杠 (-) 和双斜杠 (--)。
要停止 MongoDB 实例,NoSQLUnit 使用 Java Mongo API 向服务器发送 shutdown 命令。
配置 remote 方法不需要特殊的规则,因为您(或像 Maven 这样的系统)负责启动和停止服务器。这种模式用于部署测试,你在真实环境中测试应用程序。
配置 MongoDB 连接 下一步是配置 MongoDB 规则,负责通过插入和删除定义的数据集将 MongoDB 数据库保持在已知状态。你必须注册 MongoDbRule JUnit 规则类,这需要包含主机、端口或数据库名称等信息的配置参数。
为了使开发者的生活更轻松并使代码更具可读性,可以使用流式接口来创建这些配置对象。存在两种不同类型的配置构建器。
第一种是用于配置到内存 Fongo 服务器的连接。对于几乎所有情况,默认参数就足够了。
import static com.lordofthejars.nosqlunit.mongodb.InMemoryMongoDbConfigurationBuilder.inMemoryMongoDb;
@Rule
public MongoDbRule embeddedMongoDbRule = newMongoDbRule().defaultEmbeddedMongoDb("test" );
第二种是用于配置到托管/远程 MongoDB 服务器的连接。默认值为:
Host: localhost
Port: 27017
Authentication: 无认证参数。
import static com.lordofthejars.nosqlunit.mongodb.MongoDbConfigurationBuilder.mongoDb;
@Rule
public MongoDbRule remoteMongoDbRule = new MongoDbRule (mongoDb().databaseName("test" ).build());
@Rule
public MongoDbRule remoteMongoDbRule = new MongoDbRule (mongoDb().databaseName("test" ).host("my_remote_host" ).build());
但我们也可以将其定义为使用 Spring Data MongoDB 定义的实例。
如果你计划使用 Spring Data MongoDB ,你可能需要使用 Spring Application Context 中定义的 Mongo 实例,主要是因为你使用 Fongo 定义了嵌入式连接:
<bean name ="fongo" class ="com.foursquare.fongo.Fongo" >
<constructor-arg value ="InMemoryMongo" />
</bean >
<bean id ="mongo" factory-bean ="fongo" factory-method ="getMongo" />
在这些情况下,你应该使用一种特殊方法来获取 Mongo*****Fongo *** 实例,而不是创建新实例。
@Autowired
private ApplicationContext applicationContext;
@Rule
public MongoDbRule mongoDbRule = newMongoDbRule().defaultSpringMongoDb("test" );
注意
你需要自动注入应用程序上下文,以便 NoSQLUnit 可以将在应用程序上下文中定义的实例注入到 MongoDbRule 中。
完整示例 考虑一个图书馆应用程序,除了多种操作外,它还允许我们将新书添加到系统中。我们的模型很简单:
public class Book {
private String title;
private int numberOfPages;
public Book (String title, int numberOfPages) {
super ();
this .title = title;
this .numberOfPages = numberOfPages;
}
}
接下来的业务类负责管理对 MongoDb 服务器的访问:
public class BookManager {
private static final Logger LOGGER = LoggerFactory.getLogger(BookManager.class);
private static final MongoDbBookConverter MONGO_DB_BOOK_CONVERTER = new MongoDbBookConverter ();
private static final DbObjectBookConverter DB_OBJECT_BOOK_CONVERTER = new DbObjectBookConverter ();
private DBCollection booksCollection;
public BookManager (DBCollection booksCollection) {
this .booksCollection = booksCollection;
}
public void create (Book book) {
DBObject dbObject = MONGO_DB_BOOK_CONVERTER.convert(book);
booksCollection.insert(dbObject);
}
}
现在到了测试的时候。在接下来的测试中,我们将验证书籍是否正确插入到数据库中。
package com.lordofthejars.nosqlunit.demo.mongodb;
public class WhenANewBookIsCreated {
@ClassRule
public static ManagedMongoDb managedMongoDb = newManagedMongoDbRule().mongodPath("/opt/mongo" ).build();
@Rule
public MongoDbRule remoteMongoDbRule = new MongoDbRule (mongoDb().databaseName("test" ).build());
@Test
@UsingDataSet(locations="initialData.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
@ShouldMatchDataSet(location="expectedData.json")
public void book_should_be_inserted_into_repository () {
BookManager bookManager = new BookManager (MongoDbUtil.getCollection(Book.class.getSimpleName()));
Book book = new Book ("The Lord Of The Rings" , 1299 );
bookManager.create(book);
}
}
在测试中,我们定义 MongoDB 将由测试管理,通过启动位于 /opt/mongo 的服务器实例。此外,我们在 classpath com/lordofthejars/nosqlunit/demo/mongodb/initialData.json 中设置了初始数据集 initialData.json,以及名为 expectedData.json 的预期数据集。
{
"Book" : [
{ "title" : "The Hobbit" , "numberOfPages" : 293 }
]
}
{
"Book" : [
{ "title" : "The Hobbit" , "numberOfPages" : 293 } ,
{ "title" : "The Lord Of The Rings" , "numberOfPages" : 1299 }
]
}
副本集 (Replica Set)
简介 数据库复制在 MongoDB 中增加了数据的冗余和高可用性。在 MongoDB 中,它实现了 Replica Set 架构,可以理解为更复杂的 master-slave 复制。
设置和启动副本集架构 在 NoSQLUnit 中,我们可以定义副本集架构并启动它,这样我们的测试将针对副本集服务器执行,而不是单个服务器。由于副本集系统的性质,我们只能创建托管服务器的副本集。
主类是 ReplicaSetManagedMongoDb ,它管理副本集中所有服务器的生命周期。要构建 ReplicaSetManagedMongoDb 类,提供了 ReplicaSetBuilder 构建器类,它将允许我们定义副本集架构。使用它,我们可以设置合格服务器(可以是主节点或从节点的服务器)、仅从节点服务器、仲裁器、隐藏服务器,并配置它们的所有属性,如优先级、投票者或设置标签。
下面是一个示例,我们在名为 rs-test 的副本集中定义两个合格服务器和一个仲裁器。
import static com.lordofthejars.nosqlunit.mongodb.replicaset.ReplicaSetBuilder.replicaSet;
@ClassRule
public static ReplicaSetManagedMongoDb replicaSetManagedMongoDb = replicaSet(
"rs-test" )
.eligible(
newManagedMongoDbLifecycle().port(27017 )
.dbRelativePath("rs-0" ).logRelativePath("log-0" )
.get())
.eligible(
newManagedMongoDbLifecycle().port(27018 )
.dbRelativePath("rs-1" ).logRelativePath("log-1" )
.get())
.arbiter(
newManagedMongoDbLifecycle().port(27019 )
.dbRelativePath("rs-2" ).logRelativePath("log-2" )
.get())
.get();
注意,你必须为每个服务器定义不同的端口,并且还要定义不同的数据库路径。另外请注意,ReplicaSetManagedMongoDb 不会在副本集变得稳定之前开始执行测试(这可能需要几分钟)。
然后我们只需要像往常一样创建一个 MongoDbRule ,它将填充定义的数据到副本集服务器。对于这种情况,提供了一个新的配置构建器,允许我们定义 mongo 服务器位置和 seeding 阶段使用的写入关注点。默认使用 Aknownledge 写入关注点。
import static com.lordofthejars.nosqlunit.mongodb.ReplicationMongoDbConfigurationBuilder.replicationMongoDbConfiguration;
@Rule
public MongoDbRule mongoDbRule = newMongoDbRule().configure(
replicationMongoDbConfiguration()
.databaseName("test" )
.seed("localhost" , 27017 )
.seed("localhost" , 27018 )
.configure())
.build();
现在我们已经配置并部署了副本集,并用数据集填充了它们。
但 NoSQLUnit 还提供了一个实用方法来导致服务器故障。只需调用 shutdownServer 方法即可。
replicaSetManagedMongoDb.shutdownServer(27017 );
你也可以使用 NoSQLUnit 来测试你的远程服务器的副本集部署。你可以使用 MongoDbCommands 检索副本集配置。
DBObject replicaSetGetStatus = MongoDbCommands.replicaSetGetStatus(mongoClient);
分片 (Sharding)
简介 分片是另一种复制方式,但在本例中我们是水平扩展。MongoDB 分区集合并将不同部分存储在不同的机器上。从逻辑概览来看,客户端只看到一个单一的数据库,但内部使用的是集群机器,数据分散在整个系统中。
要运行分片,我们必须设置分片集群。分片集群由以下元素组成:
shards:保存数据库集合部分的 mongod 实例。
config servers:存储集群元数据的服务器。
mongos 服务器:确定所需数据的位置。
除了设置分片架构外,我们还必须注册每个分片,启用数据库的分片,启用我们要分区的每个集合的分片,并定义用于计算分片键的文档元素。
设置和启动分片 在 NoSQLUnit 中,我们可以定义分片架构并启动它,这样我们的测试将针对它执行,而不是单个服务器。由于分片系统的性质,我们只能为托管服务器创建分片。
主类是 ShardedManagedMongoDb ,它管理分片中所有服务器的生命周期(shards, configs 和 mongos)。要构建 ShardedManagedMongoDb 类,提供了 ShardedGroupBuilder 构建器类,它将允许我们定义分片中涉及的每个服务器。
下面是一个示例,展示如何设置和启动具有两个分片、一个配置服务器和一个 mongos 的系统。
@ClassRule
public static ShardedManagedMongoDb shardedManagedMongoDb = shardedGroup()
.shard(newManagedMongoDbLifecycle().port(27018 ).dbRelativePath("rs-1" ).logRelativePath("log-1" ).get())
.shard(newManagedMongoDbLifecycle().port(27019 ).dbRelativePath("rs-2" ).logRelativePath("log-2" ).get())
.config(newManagedMongoDbLifecycle().port(27020 ).dbRelativePath("rs-3" ).logRelativePath("log-3" ).get())
.mongos(newManagedMongosLifecycle().configServer(27020 ).get())
.get();
注意,你必须为每个服务器定义不同的端口,并且还要定义不同的数据库路径。另外请注意,对于 mongos ,你必须设置配置服务器端口,并且不需要设置数据库路径。
最后,我们只需要像往常一样创建一个 MongoDbRule ,它将填充定义的数据到分片服务器。对于这种情况,我们必须使用与副本集相同的构建器,但要启用分片。请记住,在这种情况下,我们只需要注册 mongos 实例,而不需要分片或配置服务器。
@Rule
public MongoDbRule mongoDbRule = newMongoDbRule().configure(
replicationMongoDbConfiguration()
.databaseName("test" )
.enableSharding()
.seed("localhost" , 27017 )
.configure())
.build();
最后,数据集格式从标准格式更改为允许我们定义哪些属性用作分片键。让我们看一个示例:
{
"collection_name" : {
"shard-key-pattern" : [ "attribute_1" , "attribute_2" ] ,
"data" : [
{ "attribute_1" : "value_1" , "attribute_2" : value_2, "attribute_3" : "value_3" }
]
}
}
对于每个集合,你使用 shard-key-pattern 属性定义用于计算分片键的属性,最后使用 data 属性设置将插入到集合中的整个文档。
复制分片集群 (Replicated Sharded Cluster)
简介 第三种复制方式是混合的。每个分片包含一个 n 成员的副本集。并且像分片一样,至少需要一个 config 服务器和一个 mongos 服务器。
设置和启动 在 NoSQLUnit 中,我们可以定义复制分片集群架构并启动它,这样我们的测试将针对它执行,而不是单个服务器。由于复制分片集群的性质,我们只能为托管服务器创建分片。
主类是 ShardedManagedMongoDb ,它管理分片中所有服务器的生命周期(shards, configs 和 mongos)。要构建 ShardedManagedMongoDb 类,提供了 ShardedGroupBuilder 构建器类,它将允许我们定义分片中涉及的每个服务器,但与分片相比,我们需要添加一个副本集而不是分片。为此,ReplicaSetManagedMongoDb 也被使用。
下面是一个示例,展示如何设置两个复制分片集群,每个副本集有一个成员(当然在生产环境中你会更多),一个配置服务器和一个 mongos。
import static com.lordofthejars.nosqlunit.mongodb.shard.ShardedGroupBuilder.shardedGroup;
import static com.lordofthejars.nosqlunit.mongodb.replicaset.ReplicaSetBuilder.replicaSet;
@ClassRule
public static ShardedManagedMongoDb shardedManagedMongoDb = shardedGroup()
.replicaSet(replicaSet("rs-test-1" )
.eligible(
newManagedMongoDbLifecycle()
.port(27007 ).dbRelativePath("rs-0" ).logRelativePath("log-0" )
.get()
)
).get())
.replicaSet(replicaSet("rs-test-2" )
.eligible(
newManagedMongoDbLifecycle()
.port(27009 ).dbRelativePath("rs-0" ).logRelativePath("log-0" )
.get()
)
).get())
.config(newManagedMongoDbLifecycle().port(27020 ).dbRelativePath("rs-3" ).logRelativePath("log-3" ).get())
.mongos(newManagedMongosLifecycle().configServer(27020 ).get())
.get();
注意,我们正在使用 shardedGroup 的 replicaSet 方法来在分片中创建副本集,然后使用 ReplicaSetBuilder 中定义的方法来配置副本集。
最后,我们只需要像往常一样创建一个 MongoDbRule ,它将填充定义的数据到服务器。对于复制分片集群,我们可以使用与分片相同的类和数据集。
@Rule
public MongoDbRule mongoDbRule = newMongoDbRule().configure(
replicationMongoDbConfiguration()
.databaseName("test" )
.enableSharding()
.seed("localhost" , 27017 )
.configure())
.build();
Neo4j 引擎 Neo4j 是一个高性能的 NoSQL 图数据库,具有成熟和稳健数据库的所有功能。
In Memory : com.lordofthejars.nosqlunit.neo4j.InMemoryNeo4j
Embedded : com.lordofthejars.nosqlunit.neo4j.EmbeddedNeo4j
Managed Wrapping : com.lordofthejars.nosqlunit.neo4j.ManagedWrappingNeoServer
Managed : com.lordofthejars.nosqlunit.neo4j.ManagedNeoServer
NoSQLUnit Management : com.lordofthejars.nosqlunit.neo4j.Neo4jRule
Maven 设置 要在 Neo4j 中使用 NoSQLUnit ,只需添加以下依赖:
<dependency >
<groupId > com.lordofthejars</groupId >
<artifactId > nosqlunit-neo4j</artifactId >
<version > ${version.nosqlunit}</version >
</dependency >
数据集格式 Neo4j 模块中的默认数据集文件格式是 GraphML 。GraphML 是一种全面且易于使用的图文件格式。
<?xml version="1.0" encoding="UTF-8" ?>
<graphml xmlns ="http://graphml.graphdrawing.org/xmlns" >
<key id ="attr1" for ="edge" attr.name ="attr1" attr.type ="float" attr.autoindexName ="indexName" />
<key id ="attr2" for ="node" attr.name ="attr2" attr.type ="string" />
<graph id ="G" edgedefault ="directed" >
<node id ="1" >
<data key ="attr2" > value1</data >
<index name ="mynodeindex" key ="mykey" > myvalue</index >
</node >
<node id ="2" >
<data key ="attr2" > value2</data >
</node >
<edge id ="7" source ="1" target ="2" label ="label1" >
<data key ="attr1" > float</data >
<index name ="myrelindex" key ="mykey" > myvalue</index >
</edge >
</graph >
</graphml >
graphml : GraphML 文档的根元素。
key : 图元素属性的描述,你必须定义属性类型是针对节点还是关系,名称和元素类型。在我们的例子中,支持 string, int, long, float, double 和 boolean。
graph : 图表示的开始。在我们的例子中,只支持一级图。内图将被忽略。
node : 顶点表示的开始。请注意,id 0 保留用于参考节点,因此不能用作 id。
edge : 边表示的开始。Source 和 target 属性填充为节点 id。如果你想链接到参考节点,使用 0,这是根节点的 id。注意,label 属性未在 GraphML 规范的标准定义中定义;GraphML 支持向所有 GrpahML 元素添加新属性,并且添加了 label 属性以促进边标签的创建。
data : 与图元素关联的键/值数据。数据值将根据 key 元素中定义的类型进行验证。
attr.autoindexName : 此属性是可选的,只能在 key 元素中设置。它为所有节点或边的该类型属性创建给定名称的索引。
index : 此标签是可选的,并在声明它的 node 或 edge 中创建给定名称、键和值的索引。
入门指南
生命周期管理策略 第一步是确定你的测试需要哪种生命周期管理策略。根据你正在实施的测试类型(单元测试、集成测试、部署测试等),你可能需要 in-memory 方法、embedded 方法、managed 方法或 remote 方法。
In-memory 生命周期 要配置 in-memory 方法,你只需要实例化以下内容:
import static com.lordofthejars.nosqlunit.neo4j.InMemoryNeo4j.InMemoryNeo4jRuleBuilder.newInMemoryNeo4j;
@ClassRule
public static InMemoryNeo4j inMemoryNeo4j = newInMemoryNeo4j().build();
Embedded 生命周期 要配置 embedded 方法,你只需要实例化以下内容:
import static com.lordofthejars.nosqlunit.neo4j.EmbeddedNeo4j.EmbeddedNeo4jRuleBuilder.newEmbeddedNeo4jRule;
@ClassRule
public static EmbeddedNeo4j embeddedNeo4j = newEmbeddedNeo4jRule().build();
默认情况下,嵌入式 Neo4j 规则使用以下默认值:
Target path: 这是启动 Neo4j 服务器的目录,默认为 target/neo4j-temp。
Managed 生命周期 要配置 managed 方式,可以使用两种可能的方法:
第一种是使用 embedded database wrapped by a server 。这是一种通过网络给嵌入式数据库可见性的方法(内部我们创建了 WrappingNeoServerBootstrapper 实例):
import static com.lordofthejars.nosqlunit.neo4j.ManagedWrappingNeoServer.ManagedWrappingNeoServerRuleBuilder.newWrappingNeoServerNeo4jRule;
@ClassRule
public static ManagedWrappingNeoServer managedWrappingNeoServer = newWrappingNeoServerNeo4jRule().port(8888 ).build();
默认情况下,包装的托管 Neo4j 规则使用以下默认值,但可以按如下所示通过编程方式配置:
Target path: 启动 Neo4j 服务器的目录,默认为 target/neo4j-temp。
Port: 服务器监听传入消息的端口是 7474。
第二种策略是 启动和停止执行机器上已安装的服务器 ,通过调用 start 和 stop 命令行。接下来应该注册:
import static com.lordofthejars.nosqlunit.neo4j.ManagedNeoServer.Neo4jServerRuleBuilder.newManagedNeo4jServerRule;
@ClassRule
public static ManagedNeoServer managedNeoServer = newManagedNeo4jServerRule().neo4jPath("/opt/neo4j" ).build();
默认情况下,托管 Neo4j 规则使用以下默认值,但可以按如下所示通过编程方式配置:
Target path: 这是启动 Neo4j 进程的目录,默认为 target/neo4j-temp。
Port: 服务器监听传入消息的端口是 7474。
Neo4jPath: Neo4j 安装目录,默认从 NEO4J_HOME 系统环境变量中检索。
警告
早于 Neo4j 1.8 的版本,端口无法从命令行配置,应该在 conf/neo4j-server.properties 中手动更改端口。尽管有此限制,如果你已配置 Neo4j 通过不同端口运行,也应该在 ManagedNeoServer 规则中指定。
Remote 生命周期 配置 remote 方法不需要特殊的规则,因为您(或像 Maven 这样的系统)负责启动和停止服务器。这种模式用于部署测试,你在真实环境中测试应用程序。
配置 Neo4j 连接 下一步是配置 Neo4j 规则,负责通过插入和删除定义的数据集将 Neo4j 图保持在已知状态。你必须注册 Neo4jRule JUnit 规则类,这需要包含主机、端口、uri 或目标目录等信息的配置参数。
为了使开发者的生活更轻松并使代码更具可读性,可以使用流式接口来创建这些配置对象。存在两种不同类型的配置构建器。
In-Memory/Embedded 连接 第一种是用于配置到 in-memory/embedded Neo4j 的连接。
import static com.lordofthejars.nosqlunit.neo4j.EmbeddedNeoServerConfigurationBuilder.newEmbeddedNeoServerConfiguration;
@Rule
public Neo4jRule neo4jRule = new Neo4jRule (newEmbeddedNeoServerConfiguration().build());
如果你只注册了一个嵌入式 Neo4j 实例,调用 build 就足够了。如果你使用多个 Neo4j 嵌入式连接,targetPath 应通过使用 buildFromTargetPath 方法提供。
如果你使用 in-memory 方法与 embedded 方法混合,in-memory 实例的目标路径可以在 InMemoryNeo4j.INMEMORY_NEO4J_TARGET_PATH 变量中找到。
Managed/Remote 连接 第二种是用于配置到远程 Neo4j 服务器的连接(在这个级别上无关紧要,它是包装的还是否)。默认值为:
import static com.lordofthejars.nosqlunit.neo4j.ManagedNeoServerConfigurationBuilder.newManagedNeoServerConfiguration;
@Rule
public Neo4jRule neo4jRule = new Neo4jRule (newManagedNeoServerConfiguration().build());
@Rule
public Neo4jRule neo4jRule = newNeo4jRule().defaultManagedNeo4j();
Spring 连接 如果你计划使用 Spring Data Neo4j ,你可能需要使用 Spring Application Context 中定义的 GraphDatabaseService ,主要是因为你使用 Spring namespace 定义了嵌入式连接:
<neo4j:config storeDirectory ="target/config-test" />
在这些情况下,你应该使用一种特殊方法来获取 GraphDatabaseService 实例,而不是创建新实例。
@Autowired
private ApplicationContext applicationContext;
@Rule
public Neo4jRule neo4jRule = newNeo4jRule().defaultSpringGraphDatabaseServiceNeo4j();
注意
你需要自动注入应用程序上下文,以便 NoSQLUnit 可以将在应用程序上下文中定义的实例注入到 Neo4jRule 中。
验证图 @ShouldMatchDataSet 也支持 Neo4j 图,但我们应该记住一些注意事项。
要比较两个图,存储的图被导出为 GraphML 格式,然后使用 XmlUnit 框架与预期的GraphML 进行比较。这种方法意味着要考虑两个方面,首先,虽然你的图不包含任何对参考节点的连接,但参考节点也会以 <node id="0"></node> 的形式出现。另一方面,id 是 Neo4j's 内部 id,所以当你编写预期文件时,记得遵循 Neo4j 遵循的相同 id 策略,以便每个节点的 id 属性可以与生成的输出正确匹配。插入节点的 id 从 1 开始(0 保留用于参考节点),同时边从 0 开始。
这种比较图的方式将来可能会改变(尽管这种策略将始终得到支持)。
正如我在前面提到的,我发现使用 @ShouldMatchDataSet 在测试中是一个不好的方法,因为测试可读性受到负面影响。所以作为一般指导,我的建议是尽可能避免在测试中使用 @ShouldMatchDataSet。
完整示例 为了展示如何使用 NoSQLUnit 与 Neo4j ,我们将创建一个非常简单的应用程序来计算 Neo 的朋友数量。
public class MatrixManager {
public enum RelTypes implements RelationshipType {
NEO_NODE, KNOWS, CODED_BY
}
private GraphDatabaseService graphDb;
public MatrixManager (GraphDatabaseService graphDatabaseService) {
this .graphDb = graphDatabaseService;
}
public int countNeoFriends () {
Node neoNode = getNeoNode();
Traverser friendsTraverser = getFriends(neoNode);
return friendsTraverser.getAllNodes().size();
}
public void addNeoFriend (String name, int age) {
Transaction tx = this .graphDb.beginTx();
try {
Node friend = this .graphDb.createNode();
friend.setProperty("name" , name);
Relationship relationship = getNeoNode().createRelationshipTo(friend, RelTypes.KNOWS);
relationship.setProperty("age" , age);
tx.success();
} finally {
tx.finish();
}
}
}
对于单元测试,我们将使用 embedded 方法:
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static com.lordofthejars.nosqlunit.neo4j.EmbeddedNeo4j.EmbeddedNeo4jRuleBuilder.newEmbeddedNeo4jRule;
import static com.lordofthejars.nosqlunit.neo4j.EmbeddedNeoServerConfigurationBuilder.newEmbeddedNeoServerConfiguration;
import javax.inject.Inject;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import com.lordofthejars.nosqlunit.annotation.UsingDataSet;
import com.lordofthejars.nosqlunit.core.LoadStrategyEnum;
import com.lordofthejars.nosqlunit.neo4j.EmbeddedNeo4j;
import com.lordofthejars.nosqlunit.neo4j.Neo4jRule;
public class WhenNeoFriendsAreRequired {
@ClassRule
public static EmbeddedNeo4j embeddedNeo4j = newEmbeddedNeo4jRule().build();
@Rule
public Neo4jRule neo4jRule = new Neo4jRule (newEmbeddedNeoServerConfiguration().build(), this );
@Inject
private GraphDatabaseService graphDatabaseService;
@Test
@UsingDataSet(locations="matrix.xml", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void all_direct_and_inderectly_friends_should_be_counted () {
MatrixManager matrixManager = new MatrixManager (graphDatabaseService);
int countNeoFriends = matrixManager.countNeoFriends();
assertThat(countNeoFriends, is(3 ));
}
}
import static com.lordofthejars.nosqlunit.neo4j.ManagedWrappingNeoServer.ManagedWrappingNeoServerRuleBuilder.newWrappingNeoServerNeo4jRule;
import static com.lordofthejars.nosqlunit.neo4j.ManagedNeoServerConfigurationBuilder.newManagedNeoServerConfiguration;
import javax.inject.Inject;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import com.lordofthejars.nosqlunit.annotation.ShouldMatchDataSet;
import com.lordofthejars.nosqlunit.annotation.UsingDataSet;
import com.lordofthejars.nosqlunit.core.LoadStrategyEnum;
import com.lordofthejars.nosqlunit.neo4j.ManagedWrappingNeoServer;
import com.lordofthejars.nosqlunit.neo4j.Neo4jRule;
public class WhenNeoMeetsANewFriend {
@ClassRule
public static ManagedWrappingNeoServer managedWrappingNeoServer = newWrappingNeoServerNeo4jRule().build();
@Rule
public Neo4jRule neo4jRule = new Neo4jRule (newManagedNeoServerConfiguration().build(), this );
@Inject
private GraphDatabaseService graphDatabaseService;
@Test
@UsingDataSet(locations="matrix.xml", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
@ShouldMatchDataSet(location="expected-matrix.xml")
public void friend_should_be_related_into_neo_graph () {
MatrixManager matrixManager = new MatrixManager (graphDatabaseService);
matrixManager.addNeoFriend("The Oracle" , 4 );
}
}
注意,在这两种情况下,我们都使用相同的数据集作为初始状态,看起来像这样:
<graphml xmlns ="http://graphml.graphdrawing.org/xmlns" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd" >
<key id ="name" for ="node" attr.name ="name" attr.type ="string" />
<key id ="age" for ="edge" attr.name ="age" attr.type ="int" />
<graph id ="G" edgedefault ="directed" >
<node id ="1" > <data key ="name" > Thomas Anderson</data > </node >
<node id ="2" > <data key ="name" > Trinity</data > </node >
<node id ="3" > <data key ="name" > Morpheus</data > </node >
<node id ="4" > <data key ="name" > Agent Smith</data > </node >
<node id ="5" > <data key ="name" > The Architect</data > </node >
<edge id ="1" source ="0" target ="1" label ="NEO_NODE" > </edge >
<edge id ="2" source ="1" target ="2" label ="KNOWS" > <data key ="age" > 3</data > </edge >
<edge id ="3" source ="1" target ="3" label ="KNOWS" > <data key ="age" > 5</data > </edge >
<edge id ="4" source ="2" target ="3" label ="KNOWS" > <data key ="age" > 18</data > </edge >
<edge id ="5" source ="3" target ="4" label ="KNOWS" > <data key ="age" > 20</data > </edge >
<edge id ="6" source ="4" target ="5" label ="CODED_BY" > <data key ="age" > 20</data > </edge >
</graph >
</graphml >
Cassandra 引擎 Cassandra 是在 Amazon Dynamo-like 基础设施上运行的 BigTable 数据模型。
NoSQLUnit 通过以下类支持 Cassandra :
Embedded : com.lordofthejars.nosqlunit.cassandra.EmbeddedCassandra
Managed : com.lordofthejars.nosqlunit.cassandra.ManagedCassandra
NoSQLUnit Management : com.lordofthejars.nosqlunit.cassandra.CassandraRule
Maven 设置 要在 Cassandra 中使用 NoSQLUnit ,只需添加以下依赖:
<dependency >
<groupId > com.lordofthejars</groupId >
<artifactId > nosqlunit-cassandra</artifactId >
<version > ${version.nosqlunit}</version >
</dependency >
数据集格式 Cassandra 模块中的默认数据集文件格式是 json。为了使 NoSQLUnit 与文件格式兼容,使用了 Cassandra-Unit 项目的 DataLoader,因此使用相同的 json 格式文件。
{
"name" : "" ,
"replicationFactor" : "" ,
"strategy" : "" ,
"columnFamilies" : [ {
"name" : "" ,
"type" : "" ,
"keyType" : "" ,
"comparatorType" : "" ,
"subComparatorType" : "" ,
"defaultColumnValueType" : "" ,
"comment" : "" ,
"compactionStrategy" : "" ,
"compactionStrategyOptions" : [ {
"name" : "" ,
"value" : ""
} ] ,
"gcGraceSeconds" : "" ,
"maxCompactionThreshold" : "" ,
"minCompactionThreshold" : "" ,
"readRepairChance" : "" ,
"replicationOnWrite" : "" ,
"columnsMetadata" : [ {
"name" : "" ,
"validationClass : " ",
" indexType" : " ",
" indexName" : " "
}, ...],
" rows" : [{
" key" : " ",
" columns" : [{
" name" : " ",
" value" : " "
}, ...],
... // OR
...
" superColumns" : [{
" name" : " ",
" columns" : [{
" name" : " ",
" value" : " "
}, ...]
}, ...]
}, ...]
}, ...]
}
入门指南
生命周期管理策略 第一步是确定你的测试需要哪种生命周期管理策略。根据你正在实施的测试类型(单元测试、集成测试、部署测试等),你可能需要 embedded 方法、managed 方法或 remote 方法。
Embedded 生命周期 要配置 embedded 方法,你只需要实例化以下内容:
@ClassRule
public static EmbeddedCassandra embeddedCassandraRule = newEmbeddedCassandraRule().build();
默认情况下,嵌入式 Cassandra 规则使用以下默认值:
Target path: 这是启动 Cassandra 服务器的目录,默认为 target/cassandra-temp。
Cassandra Configuration File: yaml 配置文件的位置。默认情况下,提供了一个带有正确默认参数的配置文件。
Host: localhost
Port: 默认使用的端口是 9171。端口无法配置,如果你提供替代的 Cassandra 配置文件,也不能更改。
Managed 生命周期 要配置 managed 方法,你只需要实例化以下内容:
@ClassRule
public static ManagedCassandra managedCassandra = newManagedCassandraRule().build();
默认情况下,托管 Cassandra 规则使用以下默认值,但可以通过编程方式配置:
Target path: 这是启动 Cassandra 服务器的目录,默认为 target/cassandra-temp。
CassandraPath: Cassandra 安装目录,默认从 CASSANDRA_HOME 系统环境变量中检索。
Port: 默认使用的端口是 9160。如果在 Cassandra 配置文件中更改了端口,这里也应该配置此端口。
警告
要启动 Cassandra,必须设置 java.home。通常此变量已经配置好,你不需要做任何事情。
Remote 生命周期 配置 remote 方法不需要特殊的规则,因为您(或像 Maven 这样的系统)负责启动和停止服务器。这种模式用于部署测试,你在真实环境中测试应用程序。
配置 Cassandra 连接 下一步是配置 Cassandra 规则,负责通过插入和删除定义的数据集将 Cassandra 图保持在已知状态。你必须注册 CassandraRule JUnit 规则类,这需要包含主机、端口或集群名称等信息的配置参数。
为了使开发者的生活更轻松并使代码更具可读性,可以使用流式接口来创建这些配置对象。存在三种不同类型的配置构建器。
Embedded 连接 第一种是用于配置到嵌入式 Cassandra 的连接。
import static com.lordofthejars.nosqlunit.cassandra.EmbeddedCassandraConfigurationBuilder.newEmbeddedCassandraConfiguration;
@Rule
public CassandraRule cassandraRule = new CassandraRule (newEmbeddedCassandraConfiguration().clusterName("Test Cluster" ).build());
Managed 连接 第一种是用于配置到托管 Cassandra 的连接。
import static com.lordofthejars.nosqlunit.cassandra.ManagedCassandraConfigurationBuilder.newManagedCassandraConfiguration;
@Rule
public CassandraRule cassandraRule = new CassandraRule (newManagedCassandraConfiguration().clusterName("Test Cluster" ).build());
Host 和 port 参数已经使用托管生命周期的默认参数配置好了。如果端口更改,此类提供了一种方法来设置它。
Remote 连接 import static com.lordofthejars.nosqlunit.cassandra.RemoteCassandraConfigurationBuilder.newRemoteCassandraConfiguration;
@Rule
public CassandraRule cassandraRule = new CassandraRule (newRemoteCassandraConfiguration().host("192.168.1.1" ).clusterName("Test Cluster" ).build());
Port 参数已经使用托管生命周期的默认参数配置好了。如果端口更改,此类提供了一种方法来设置它。注意,在这种情况下必须指定 host 参数。
验证数据 @ShouldMatchDataSet 也支持 Cassandra 数据,但我们应该记住一些注意事项。
警告
在 NoSQLUnit 中,期望只能用于数据,不能用于配置参数,例如数据集中的字段如 compactionStrategy, gcGraceSeconds 或 maxCompactionThreshold 不使用。也许将来会支持,但目前仅支持数据(keyspace, columnfamilyname, columns, supercolumns, ...)。
完整示例 为了展示如何使用 NoSQLUnit 与 Cassandra ,我们将创建一个非常简单的应用程序。
public class PersonManager {
private ColumnFamilyTemplate<String, String> template;
public PersonManager (String clusterName, String keyspaceName, String host) {
Cluster cluster = HFactory.getOrCreateCluster(clusterName, host);
Keyspace keyspace = HFactory.createKeyspace(keyspaceName, cluster);
template = new ThriftColumnFamilyTemplate <String, String>(keyspace,
"personFamilyName" , StringSerializer.get(), StringSerializer.get());
}
public String getCarByPersonName (String name) {
ColumnFamilyResult<String, String> queryColumns = template.queryColumns(name);
return queryColumns.getString("car" );
}
public void updateCarByPersonName (String name, String car) {
ColumnFamilyUpdater<String, String> createUpdater = template.createUpdater(name);
createUpdater.setString("car" , car);
template.update(createUpdater);
}
}
对于单元测试,我们将使用 embedded 方法:
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static com.lordofthejars.nosqlunit.cassandra.EmbeddedCassandra.EmbeddedCassandraRuleBuilder.newEmbeddedCassandraRule;
import static com.lordofthejars.nosqlunit.cassandra.EmbeddedCassandraConfigurationBuilder.newEmbeddedCassandraConfiguration;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import com.lordofthejars.nosqlunit.annotation.UsingDataSet;
import com.lordofthejars.nosqlunit.cassandra.CassandraRule;
import com.lordofthejars.nosqlunit.cassandra.EmbeddedCassandra;
import com.lordofthejars.nosqlunit.core.LoadStrategyEnum;
public class WhenPersonWantsToKnowItsCar {
@ClassRule
public static EmbeddedCassandra embeddedCassandraRule = newEmbeddedCassandraRule().build();
@Rule
public CassandraRule cassandraRule = new CassandraRule (newEmbeddedCassandraConfiguration().clusterName("Test Cluster" ).build());
@Test
@UsingDataSet(locations="persons.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void car_should_be_returned () {
PersonManager personManager = new PersonManager ("Test Cluster" , "persons" , "localhost:9171" );
String car = personManager.getCarByPersonName("mary" );
assertThat(car, is("ford" ));
}
}
import static com.lordofthejars.nosqlunit.cassandra.ManagedCassandraConfigurationBuilder.newManagedCassandraConfiguration;
import static com.lordofthejars.nosqlunit.cassandra.ManagedCassandra.ManagedCassandraRuleBuilder.newManagedCassandraRule;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import com.lordofthejars.nosqlunit.annotation.ShouldMatchDataSet;
import com.lordofthejars.nosqlunit.annotation.UsingDataSet;
import com.lordofthejars.nosqlunit.cassandra.CassandraRule;
import com.lordofthejars.nosqlunit.cassandra.ManagedCassandra;
import com.lordofthejars.nosqlunit.core.LoadStrategyEnum;
public class WhenPersonWantsToUpdateItsCar {
static {
System.setProperty("CASSANDRA_HOME" , "/opt/cassandra" );
}
@ClassRule
public static ManagedCassandra managedCassandra = newManagedCassandraRule().build();
@Rule
public CassandraRule cassandraRule = new CassandraRule (newManagedCassandraConfiguration().clusterName("Test Cluster" ).build());
@Test
@UsingDataSet(locations="persons.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
@ShouldMatchDataSet(location="expected-persons.json")
public void new_car_should_be_updated () {
PersonManager personManager = new PersonManager ("Test Cluster" , "persons" , "localhost:9171" );
personManager.updateCarByPersonName("john" , "opel" );
}
}
注意,在这两种情况下,我们都使用相同的数据集作为初始状态,看起来像这样:
{
"name" : "persons" ,
"columnFamilies" : [ {
"name" : "personFamilyName" ,
"keyType" : "UTF8Type" ,
"defaultColumnValueType" : "UTF8Type" ,
"comparatorType" : "UTF8Type" ,
"rows" : [ {
"key" : "john" ,
"columns" : [ {
"name" : "age" ,
"value" : "22"
} , {
"name" : "car" ,
"value" : "toyota"
} ]
} , {
"key" : "mary" ,
"columns" : [ {
"name" : "age" ,
"value" : "33"
} , {
"name" : "car" ,
"value" : "ford"
} ]
} ]
} ]
}
Redis 引擎 Redis 是一个开源的高级键值存储。它通常被称为数据结构服务器,因为键可以包含字符串、哈希、列表、集合和排序集合。
Embedded : com.lordofthejars.nosqlunit.redis.EmbeddedRedis
Managed : com.lordofthejars.nosqlunit.redis.ManagedRedis
NoSQLUnit Management : com.lordofthejars.nosqlunit.redis.RedisRule
Maven 设置 要在 Redis 中使用 NoSQLUnit ,只需添加以下依赖:
<dependency >
<groupId > com.lordofthejars</groupId >
<artifactId > nosqlunit-redis</artifactId >
<version > ${version.nosqlunit}</version >
</dependency >
数据集格式 Redis 模块中的默认数据集文件格式是 json。
{ "data" : [
{ "simple" : [ { "key" : "key1" , "value" : "value1" } ] } ,
{ "list" : [ { "key" : "key3" , "values" : [ { "value" : "value3" } , { "value" : "value4" } ] } ] } ,
{ "sortset" : [ { "key" : "key4" , "values" : [ { "score" : 2 , "value" : "value5" } , { "score" : 3 , "value" : 1 } , { "score" : 1 , "value" : "value6" } ] } ] } ,
{ "hash" : [ { "key" : "user" , "values" : [ { "field" : "name" , "value" : "alex" } , { "field" : "password" , "value" : "alex" } ] } ] } ,
{ "set" : [ { "key" : "key3" , "values" : [ { "value" : "value3" } , { "value" : "value4" } ] } ] }
] }
根元素必须称为 data ,然后根据我们需要存储的结构化数据类型,应该出现一个或多个以下元素。注意,key 字段用于设置元素的键,value 字段用于设置值。
simple : 如果我们想存储简单的键/值元素。此元素将包含键/值条目的数组。
list : 如果我们想存储带有值列表的键。此元素包含一个 key 字段用于键名和 values 字段用于值数组。
set : 如果我们想存储集合中的键(不允许重复)。结构与 list 元素相同。
sortset : 如果我们想存储排序集合中的键。此元素包含键和值数组,每个值除了 value 字段外,还包含类型为 Number 的 score 字段,以设置排序集合中的顺序。
hash : 如果我们想存储字段/值映射中的键。在这种情况下,field 元素设置字段名,value 设置该字段的值。
入门指南
生命周期管理策略 第一步是确定你的测试需要哪种生命周期管理策略。根据你正在实施的测试类型(单元测试、集成测试、部署测试等),你可能需要 embedded 方法、managed 方法或 remote 方法。
Embedded 生命周期 要配置 embedded 方法,你只需要实例化以下内容:
@ClassRule
public static EmbeddedRedis embeddedRedis = newEmbeddedRedisRule().build();
默认情况下,托管 Redis 规则使用以下默认值,但可以通过编程方式配置:
Target path: 这是启动嵌入式 Redis 实例的目录,默认为 target/redis-test-data/impermanent-db。
注意,目标路径仅用作配置参数,以允许多个嵌入式内存 Redis 引擎实例。
Managed 生命周期 要配置 managed 方法,你只需要实例化以下内容:
@ClassRule
public static ManagedRedis managedRedis = newManagedRedisRule().redisPath("/opt/redis-2.4.16" ).build();
默认情况下,托管 Redis 规则使用以下默认值,但可以通过编程方式配置:
Target path: 这是启动 Redis 服务器的目录,默认为 target/redis-temp。
RedisPath: Cassandra 安装目录,默认从 REDIS_HOME 系统环境变量中检索。
Port: 默认使用的端口是 6379。如果在 Redis 配置文件中更改了端口,这里也应该配置此端口。
Configuration File: 默认情况下 Redis 可以不使用配置文件工作,它使用默认值,但如果我们需要启动带有特定配置文件的 Redis ,则应设置位于任何目录的文件路径。
Remote 生命周期 配置 remote 方法不需要特殊的规则,因为您(或像 Maven 这样的系统)负责启动和停止服务器。这种模式用于部署测试,你在真实环境中测试应用程序。
配置 Redis 连接 下一步是配置 Redis 规则,负责通过插入和删除定义的数据集将 Redis 存储保持在已知状态。你必须注册 RedisRule JUnit 规则类,这需要包含主机、端口或集群名称等信息的配置参数。
为了使开发者的生活更轻松并使代码更具可读性,可以使用流式接口来创建这些配置对象。存在三种不同类型的配置构建器。
Embedded 连接 第一种是用于配置到托管 Redis 的嵌入式连接。
import static com.lordofthejars.nosqlunit.redis.RedisRule.RedisRuleBuilder.newRedisRule;
@Rule
public RedisRule redisRule = newRedisRule().defaultEmbeddedRedis();
Managed 连接 import static com.lordofthejars.nosqlunit.redis.ManagedRedisConfigurationBuilder.newManagedRedisConfiguration;
@Rule
public RedisRule redisRule = new RedisRule (newManagedRedisConfiguration().build());
Host 和 port 参数已经使用托管生命周期的默认参数配置好了。如果端口更改,此类提供了一种方法来设置它。
Remote 连接 import static com.lordofthejars.nosqlunit.redis.RemoteRedisConfigurationBuilder.newRemoteRedisConfiguration;
@Rule
public RedisRule redisRule = new RedisRule (newRemoteRedisConfiguration().host("192.168.1.1" ).build());
Port 参数已经使用托管生命周期的默认参数配置好了。如果端口更改,此类提供了一种方法来设置它。注意,在这种情况下必须指定 host 参数。
Shard 连接 Redis 连接也可以使用 ShardedJedis 能力配置为 shard。
import static com.lordofthejars.nosqlunit.redis.RemoteRedisConfigurationBuilder.newRemoteRedisConfiguration;
@Rule
public RedisRule redisRule = new RedisRule (newShardedRedisConfiguration()
.shard(host("127.0.0.1" ), port(ManagedRedis.DEFAULT_PORT))
.password("a" )
.timeout(1000 )
.weight(1000 )
.shard(host("127.0.0.1" ), port(ManagedRedis.DEFAULT_PORT + 1 ))
.password("b" )
.timeout(3000 )
.weight(3000 )
.build());
注意,只有 host 和 port 是强制性的,其他使用默认值。
password : 如果存储库受密码保护,此属性用作密码。默认值为 null。
timeout : 分片的超时时间。默认超时设置为 2 秒。
weight : 该分片相对于其他分片的权重。默认为 1。
验证数据 @ShouldMatchDataSet 也支持 Redis 引擎。
完整示例 为了展示如何使用 NoSQLUnit 与 Redis ,我们将创建一个非常简单的应用程序。
public class BookManager {
private static final String TITLE_FIELD_NAME = "title" ;
private static final String NUMBER_OF_PAGES = "numberOfPages" ;
private Jedis jedis;
public BookManager (Jedis jedis) {
this .jedis = jedis;
}
public void insertBook (Book book) {
Map<String, String> fields = new HashMap <String, String>();
fields.put(TITLE_FIELD_NAME, book.getTitle());
fields.put(NUMBER_OF_PAGES, Integer.toString(book.getNumberOfPages()));
jedis.hmset(book.getTitle(), fields);
}
public Book findBookByTitle (String title) {
Map<String, String> fields = jedis.hgetAll(title);
return new Book (fields.get(TITLE_FIELD_NAME), Integer.parseInt(fields.get(NUMBER_OF_PAGES)));
}
}
import static com.lordofthejars.nosqlunit.redis.RedisRule.RedisRuleBuilder.newRedisRule;
import static com.lordofthejars.nosqlunit.redis.ManagedRedis.ManagedRedisRuleBuilder.newManagedRedisRule;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import com.lordofthejars.nosqlunit.annotation.UsingDataSet;
import com.lordofthejars.nosqlunit.core.LoadStrategyEnum;
import com.lordofthejars.nosqlunit.demo.model.Book;
import com.lordofthejars.nosqlunit.redis.ManagedRedis;
import com.lordofthejars.nosqlunit.redis.RedisRule;
public class WhenYouFindABook {
static {
System.setProperty("REDIS_HOME" , "/opt/redis-2.4.16" );
}
@ClassRule
public static ManagedRedis managedRedis = newManagedRedisRule().build();
@Rule
public RedisRule redisRule = newRedisRule().defaultManagedRedis();
@Test
@UsingDataSet(locations="book.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void book_should_be_returned_if_title_is_in_database () {
BookManager bookManager = new BookManager (new Jedis ("localhost" ));
Book findBook = bookManager.findBookByTitle("The Hobbit" );
assertThat(findBook, is(new Book ("The Hobbit" , 293 )));
}
}
{ "data" : [ { "hash" : [ { "key" : "The Hobbit" , "values" : [ { "field" : "title" , "value" : "The Hobbit" } , { "field" : "numberOfPages" , "value" : "293" } ] } ] } ] }
HBase 引擎 Apache HBase 是一个开源的、分布式的、版本控制的、面向列的存储。
Embedded : com.lordofthejars.nosqlunit.hbase.EmbeddedHBase
Managed : com.lordofthejars.nosqlunit.hbase.ManagedHBase
NoSQLUnit Management : com.lordofthejars.nosqlunit.hbase.HBaseRule
Maven 设置 要在 HBase 中使用 NoSQLUnit ,只需添加以下依赖:
<dependency >
<groupId > com.lordofthejars</groupId >
<artifactId > nosqlunit-hbase</artifactId >
<version > ${version.nosqlunit}</version >
</dependency >
数据集格式 HBase 模块中的默认数据集文件格式是 json。HBase 中的数据集与 TSV HBase 应用程序相同,但不是所有字段都受支持。只有 TSV HBase 应用程序中可用的字段才能设置到数据集中。
{
"name" : "tablename" ,
"columnFamilies" : [ {
"name" : "columnFamilyName" ,
"rows" : [ {
"key" : "key1" ,
"columns" : [ {
"name" : "columnName" ,
"value" : "columnValue"
} , ...]
} , ...]
} , ...]
}
入门指南
生命周期管理策略 第一步是确定你的测试需要哪种生命周期管理策略。根据你正在实施的测试类型(单元测试、集成测试、部署测试等),你可能需要 embedded 方法、managed 方法或 remote 方法。
Embedded 生命周期 要配置 embedded 方法,你只需要实例化以下内容:
@ClassRule
public static EmbeddedHBase embeddedHBase = newEmbeddedHBaseRule().build();
默认情况下,嵌入式 Embedded 规则使用 HBaseTestingUtility 默认值:
Target path: 这是 HBase 存储数据的目录,默认为 target/data。
Host: localhost
Port: 默认使用的端口是 60000。
File Permissions: 根据你的 umask 配置,HBaseTestingUtility 将创建一些目录,这些目录在运行时可能无法访问。默认此值设置为 775,但根据你的操作系统,你可能需要不同的值。
Managed 生命周期 要配置 managed 方法,你只需要实例化以下内容:
@ClassRule
public static ManagedHBase managedHBase = newManagedHBaseServerRule().build();
默认情况下,托管 HBase 规则使用以下默认值,但可以通过编程方式配置:
Target path: 这是启动 HBase 服务器的目录,默认为 target/hbase-temp。
HBasePath:HBase 安装目录,默认从 HBASE_HOME 系统环境变量中检索。
Port: 默认使用的端口是 60000。如果在 HBase 配置文件中更改了端口,这里也应该配置此端口。
警告
要启动 HBASE,必须设置 JAVA_HOME。通常此变量已经配置好,所以你不需要做任何事情。
Remote 生命周期 配置 remote 方法不需要特殊的规则,因为您(或像 Maven 这样的系统)负责启动和停止服务器。这种模式用于部署测试,你在真实环境中测试应用程序。
配置 HBase 连接 下一步是配置 HBase 规则,负责通过插入和删除定义的数据集将 HBase 列保持在已知状态。你必须注册 HBaseRule JUnit 规则类,这需要包含一些信息的配置参数。
为了使开发者的生活更轻松并使代码更具可读性,可以使用流式接口来创建这些配置对象。存在三种不同类型的配置构建器。
Embedded 连接 import static com.lordofthejars.nosqlunit.hbase.EmbeddedHBase.EmbeddedHBaseRuleBuilder.newEmbeddedHBaseRule;
@Rule
public HBaseRule hBaseRule = newHBaseRule().defaultEmbeddedHBase();
嵌入式 HBase 不需要任何特殊参数。配置对象直接从 Embedded 规则复制到 HBaseRule。
Managed 连接 import static com.lordofthejars.nosqlunit.hbase.ManagedHBaseConfigurationBuilder.newManagedHBaseConfiguration;
@Rule
public HBaseRule hbaseRule = new HBaseRule (newManagedHBaseConfiguration().build());
默认配置是使用调用 HBaseConfiguration.create() 方法加载的配置,它使用 hbase-site.xml 和 hbase-default.xml classpath 文件。
但也提供了一个 setProperty 方法来修改生成配置对象的任何参数。
Remote 连接 配置到远程 HBase 的连接使用与 ManagedHBase 配置对象相同的方法,但使用 com.lordofthejars.nosqlunit.hbase.RemoteHBaseConfigurationBuilder 类代替 com.lordofthejars.nosqlunit.hbase.ManagedHBaseConfigurationBuilder。
警告
与 Apache HBase 一起工作需要一些关于其工作原理的知识。例如,你的 /etc/hosts 文件不能包含指向你的主机名的引用,IP 为 127.0.1.1。
此外,NoSQLUnit 使用 HBase-0.94.1 ,并且此版本也应安装在你的计算机上才能与托管或远程方法一起工作。如果你安装了另一个版本,你应该从 NoSQLUnit 依赖项中排除这些工件,并手动将它们添加到你的 pom 文件中。
验证数据 @ShouldMatchDataSet 也支持 HBase 数据,但我们应该记住一些注意事项。
如果你计划在托管和远程方法中使用 @ShouldMatchDataSet 验证数据,你应该通过编辑 hbase-site-xml 文件并添加以下行来启用 Aggregate coprocessor:
<property >
<name > hbase.coprocessor.user.region.classes</name >
<value > org.apache.hadoop.hbase.coprocessor.AggregateImplementation</value >
</property >
完整示例 为了展示如何使用 NoSQLUnit 与 HBase ,我们将创建一个非常简单的应用程序。
public class PersonManager {
private Configuration configuration;
public PersonManager (Configuration configuration) {
this .configuration = configuration;
}
public String getCarByPersonName (String personName) throws IOException {
HTable table = new HTable (configuration, "person" );
Get get = new Get ("john" .getBytes());
Result result = table.get(get);
return new String (result.getValue(toByteArray().convert("personFamilyName" ), toByteArray().convert("car" )));
}
private Converter<String, byte []> toByteArray() {
return new Converter <String, byte []>() {
@Override
public byte [] convert(String element) {
return element.getBytes();
}
};
}
}
对于单元测试,我们将使用 embedded 方法:
public class WhenPersonWantsToKnowItsCar {
@ClassRule
public static EmbeddedHBase embeddedHBase = newEmbeddedHBaseRule().build();
@Rule
public HBaseRule hBaseRule = newHBaseRule().defaultEmbeddedHBase(this );
@Inject
private Configuration configuration;
@Test
@UsingDataSet(locations="persons.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void car_should_be_returned () throws IOException {
PersonManager personManager = new PersonManager (configuration);
String car = personManager.getCarByPersonName("john" );
assertThat(car, is("toyota" ));
}
}
{
"name" : "person" ,
"columnFamilies" : [ {
"name" : "personFamilyName" ,
"rows" : [ {
"key" : "john" ,
"columns" : [ {
"name" : "age" ,
"value" : "22"
} , {
"name" : "car" ,
"value" : "toyota"
} ]
} , {
"key" : "mary" ,
"columns" : [ {
"name" : "age" ,
"value" : "33"
} , {
"name" : "car" ,
"value" : "ford"
} ]
} ]
} ]
}
CouchDB 引擎 CouchDB 是一种 NoSQL 数据库,它以动态模式的 JSON-like 文档形式存储结构化数据。
NoSQLUnit 通过以下类支持 CouchDB :
Managed : com.lordofthejars.nosqlunit.couchdb.ManagedCouchDb
NoSQLUnit Management : com.lordofthejars.nosqlunit.couchdb.CouchDbRule
Maven 设置 要在 CouchDB 中使用 NoSQLUnit ,只需添加以下依赖:
<dependency >
<groupId > com.lordofthejars</groupId >
<artifactId > nosqlunit-couchdb</artifactId >
<version > ${version.nosqlunit}</version >
</dependency >
数据集格式 CouchDB 模块中的默认数据集文件格式是 json 。
{
"data" : [
{ "attribute1" : "value1" , "atribute2" : "value2" , ...} ,
{ ...}
]
}
入门指南
生命周期管理策略 第一步是确定你的测试需要哪种生命周期管理策略。根据你正在实施的测试类型(单元测试、集成测试、部署测试等),你可能需要 managed 方法或 remote 方法。
没有 CouchDB inmemory 实例,所以只能使用托管或远程生命周期。
要配置 managed 方式,你应该使用 ManagedCouchDb 规则,可能需要一些参数。
import static com.lordofthejars.nosqlunit.couchdb.ManagedCouchDb.ManagedCouchDbRuleBuilder.newManagedCouchDbRule;
@ClassRule
public static ManagedCouchDb managedCouchDb = newManagedCouchDbRule().couchDbPath("/usr/local" ).build();
默认情况下,托管 CouchDB 规则使用以下默认值:
CouchDB 安装目录从 COUCHDB_HOME 系统环境变量中检索。
Target path, 即启动 CouchDB 服务器的目录,是 target/couchdb-temp。
CouchDB 将启动的端口。注意,此参数仅用作信息,如果你从配置文件中更改端口,你也应该更改此参数。默认情况下,CouchDB 服务器在 5984 启动。
配置 remote 方法不需要特殊的规则,因为您(或像 Maven 这样的系统)负责启动和停止服务器。这种模式用于部署测试,你在真实环境中测试应用程序。
配置 CouchDB 连接 下一步是配置 CouchDB 规则,负责通过插入和删除定义的数据集将 CouchDB 数据库保持在已知状态。你必须注册 CouchDbRule JUnit 规则类,这需要包含主机、端口或数据库名称等信息的配置参数。
为了使开发者的生活更轻松并使代码更具可读性,可以使用流式接口来创建这些配置对象。
URI: http://localhost:5984
Authentication: 无认证参数。
Enable SSL: false.
Relaxed SSL Settings: false.
Caching: True.
import static com.lordofthejars.nosqlunit.couchdb.CouchDbRule.CouchDbRuleBuilder.newCouchDbRule;
@Rule
public CouchDbRule couchDbRule = newCouchDbRule().defaultManagedCouchDb("books" );
完整示例 考虑一个图书馆应用程序,除了多种操作外,它还允许我们将新书添加到系统中。我们的模型很简单:
public class Book {
private String title;
private int numberOfPages;
public Book (String title, int numberOfPages) {
super ();
this .title = title;
this .numberOfPages = numberOfPages;
}
}
接下来的业务类负责管理对 CouchDB 服务器的访问:
private CouchDbConnector connector;
public BookManager (CouchDbConnector connector) {
this .connector = connector;
}
public void create (Book book) {
connector.create(MapBookConverter.toMap(book));
}
public Book findBookById (String id) {
Map<String, Object> map = connector.get(Map.class, id);
return MapBookConverter.toBook(map);
}
现在到了测试的时候。在接下来的测试中,我们将验证是否在数据库中找到了书籍。
public class WhenYouFindBooksById {
@ClassRule
public static ManagedCouchDb managedCouchDb = newManagedCouchDbRule().couchDbPath("/usr/local" ).build();
@Rule
public CouchDbRule couchDbRule = newCouchDbRule().defaultManagedCouchDb("books" );
@Inject
private CouchDbConnector couchDbConnector;
@Test
@UsingDataSet(locations="books.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void identified_book_should_be_returned () {
BookManager bookManager = new BookManager (couchDbConnector);
Book book = bookManager.findBookById("1" );
assertThat(book.getTitle(), is("The Hobbit" ));
assertThat(book.getNumberOfPages(), is(293 ));
}
}
{
"data" : [
{ "_id" : "1" , "title" : "The Hobbit" , "numberOfPages" : "293" }
]
}
Infinispan 引擎 Infinispan 是一个开源的事务性内存键值 NoSQL 数据存储 & 数据网格。
NoSQLUnit 通过以下类支持 Infinispan :
Embedded : com.lordofthejars.nosqlunit.infinispan.EmbeddedInfinispan
Managed : com.lordofthejars.nosqlunit.infinispan.ManagedInfinispan
NoSQLUnit Management : com.lordofthejars.nosqlunit.infinispan.InfinispanRule
Maven 设置 要在 HBase 中使用 NoSQLUnit ,只需添加以下依赖:
<dependency >
<groupId > com.lordofthejars</groupId >
<artifactId > nosqlunit-infinispan</artifactId >
<version > ${version.nosqlunit}</version >
</dependency >
数据集格式 Infinispan 模块中的默认数据集文件格式是 json。使用此数据集,你可以定义将插入到 Infinispan 中的键和值。值可以是简单类型,如 Integer, String, ...,集合类型,如 set 和 list 实现,或者对象(使用默认的 Jackson 规则,无需注解)。
{ "data" : [
{
"key" : "alex" , "implementation" : "com.lordofthejars.nosqlunit.demo.infinispan.User" , "value" : {
"name" : "alex" ,
"age" : 32
}
} ,
{ "key" : "key1" , "value" : 1 } ,
{ "key" : "key2" , "implementation" : "java.util.HashSet" , "value" : [ { "value" : "a" } , { "value" : "b" } ]
}
] }
注意,第一个键插入了一个对象。你应该设置其实现,并以 json 格式设置对象属性,以便 Jackson 可以创建所需的对象。User 对象只包含属性的 getter 和 setter。第二个键是一个简单的键,在这种情况下是一个整数。第三个是字符串集。注意,我们必须提供集合的实现,否则将使用 ArrayList 作为默认值。你也可以定义对象而不是简单类型。
入门指南
生命周期管理策略 第一步是确定你的测试需要哪种生命周期管理策略。根据你正在实施的测试类型(单元测试、集成测试、部署测试等),你可能需要 embedded 方法、managed 方法或 remote 方法。
Embedded 生命周期 要配置 embedded 方法,你只需要实例化以下内容:
@ClassRule
public static final EmbeddedInfinispan EMBEDDED_INFINISPAN = newEmbeddedInfinispanRule().build();
默认情况下,嵌入式 Embedded 规则使用 EmbeddedCacheManager 默认值:
Target path: 这是用于启动嵌入式 Infinispan 的目录,默认为 target/infinispan-test-data/impermanent-db。
Configuration File: Infinispan 用于配置网格的配置文件。默认情况下不提供配置文件,并使用默认 Infinispan 内部值。
Managed 生命周期 要配置 managed 方法,你只需要实例化以下内容:
@ClassRule
public static ManagedInfinispan managedInfinispan = newManagedInfinispanRule().infinispanPath("/opt/infinispan-5.1.6" ).build();
默认情况下,托管 Infinispan 规则使用以下默认值,但可以通过编程方式配置:
Target path: 这是启动 Infinispan 服务器的目录,默认为 target/infinispan-temp。
InfinispanPath: Infinispan 安装目录,默认从 INFINISPAN_HOME 系统环境变量中检索。
Port: 默认使用的端口是 11222。
Protocol: 默认使用 hotrod,并且内部NoSQLUnit 也使用 hotrod,因此最好不要更改它。
Remote 生命周期 配置 remote 方法不需要特殊的规则,因为您(或像 Maven 这样的系统)负责启动和停止服务器。这种模式用于部署测试,你在真实环境中测试应用程序。
配置 Infinispan 连接 下一步是配置 Infinispan 规则,负责通过插入和删除定义的数据集将 Infinispan 列保持在已知状态。你必须注册 InfinispanRule JUnit 规则类,这需要包含一些信息的配置参数。
为了使开发者的生活更轻松并使代码更具可读性,可以使用流式接口来创建这些配置对象。存在三种不同类型的配置构建器。
Embedded 连接 第一种是用于配置到嵌入式 Infinispan 的连接。
com.lordofthejars.nosqlunit.infinispan.InfinispanRule.InfinispanRuleBuilder.newInfinispanRule;
@Rule
public InfinispanRule infinispanRule = newInfinispanRule().defaultEmbeddedInfinispan();
嵌入式 Infinispan 不需要任何特殊参数。但你可以使用 com.lordofthejars.nosqlunit.infinispan.EmbeddedInfinispanConfigurationBuilder 类来创建自定义配置对象以设置缓存名称。
Managed 连接 这是用于配置到托管 Infinispan 的连接。
import static com.lordofthejars.nosqlunit.infinispan.ManagedInfinispanConfigurationBuilder.newManagedInfinispanConfiguration;
@Rule
public InfinispanRule infinispanRule = newInfinispanRule().configure(newManagedHBaseConfiguration().build()).build();
默认使用的端口是 11222,配置使用 Infinispan 提供的默认配置。你还可以设置配置属性(hotrod 客户端使用)和缓存名称。
Remote 连接 配置到远程 Infinispan 的连接使用与 ManagedInfinispan 配置对象相同的方法,但使用 com.lordofthejars.nosqlunit.infinispan.RemoteInfinispanConfigurationBuilder 类。
验证数据 @ShouldMatchDataSet 也支持 Infinispan 数据,但我们应该记住一些注意事项。
如果你计划使用 @ShouldMatchDataSet 和 POJO 对象验证数据,则使用 equals 方法,因此请相应地实现它。
完整示例 为了展示如何使用 NoSQLUnit 与 Infinispan ,我们将创建一个非常简单的应用程序。
public class UserManager {
private BasicCache<String, User> cache;
public UserManager (BasicCache<String, User> cache) {
this .cache = cache;
}
public void addUser (User user) {
this .cache.put(user.getName(), user);
}
public User getUser (String name) {
return this .cache.get(name);
}
}
对于单元测试,我们将使用 embedded 方法:
public class WhenUserIsFoundByName {
@ClassRule
public static final EmbeddedInfinispan EMBEDDED_INFINISPAN = newEmbeddedInfinispanRule().build();
@Rule
public final InfinispanRule infinispanRule = newInfinispanRule().defaultEmbeddedInfinispan();
@Inject
private BasicCache<String, User> embeddedCache;
@Test
@UsingDataSet(locations="user.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void user_should_be_returned () {
UserManager userManager = new UserManager (embeddedCache);
User user = userManager.getUser("alex" );
assertThat(user, is(new User ("alex" , 32 )));
}
}
{ "data" : [
{
"key" : "alex" , "implementation" : "com.lordofthejars.nosqlunit.demo.infinispan.User" , "value" : {
"name" : "alex" ,
"age" : 32
}
}
] }
public class WhenUserIsInserted {
@ClassRule
public static final ManagedInfinispan MANAGED_INFINISPAN = newManagedInfinispanRule().infinispanPath("/opt/infinispan-5.1.6" ).build();
@Rule
public final InfinispanRule infinispanRule = newInfinispanRule().defaultManagedInfinispan();
@Inject
private BasicCache<String, User> remoteCache;
@UsingDataSet(loadStrategy=LoadStrategyEnum.DELETE_ALL)
@ShouldMatchDataSet(location="user.json")
@Test
public void user_should_be_available_in_cache () {
UserManager userManager = new UserManager (remoteCache);
userManager.addUser(new User ("alex" , 32 ));
}
}
Elasticsearch 引擎 ElasticSearch 是一个基于 Apache Lucene 的分布式、RESTful、免费/开源搜索服务器。
NoSQLUnit 支持 Elasticsearch 通过以下类:
In Memory : com.lordofthejars.nosqlunit.elasticsearch.EmbeddedElasticsearch
Managed : com.lordofthejars.nosqlunit.elasticsearch.ManagedElasticsearch
NoSQLUnit Management : com.lordofthejars.nosqlunit.elasticsearch.ElasticsearchRule
Maven 设置 要在 Elasticsearch 中使用 NoSQLUnit ,只需添加以下依赖:
<dependency >
<groupId > com.lordofthejars</groupId >
<artifactId > nosqlunit-elasticsearch</artifactId >
<version > ${version.nosqlunit}</version >
</dependency >
数据集格式 Elasticsearch 模块中的默认数据集文件格式是 json 。
{
"documents" : [
{
"document" : [
{
"index" : {
"indexName" : "indexName1" ,
"indexType" : "indexType1" ,
"indexId" : "indexId1"
}
} ,
{
"index" : {
"indexName" : "indexName2" ,
"indexType" : "indexType2" ,
"indexId" : "indexId2"
}
} ,
{
"data" : {
"property1" : "value1" ,
"property2" : value2
}
}
]
} ,
...
]
}
注意,如果属性值是整数,则不需要双引号。你还可以定义任意数量的 index 子文档,但只有一个 data 文档将插入到 Elasticsearch 中。此外,属性 indexId 仅在你想使用插入的数据与 @ShouldMatchDataSet 进行验证时才需要。文档索引用于比检索所有数据更快地运行比较。如果你不打算使用 NoSQLUnit 的期望功能,则不需要设置 indexId 属性,Elasticsearch 将为你提供。
入门指南
生命周期管理策略 第一步是确定你的测试需要哪种生命周期管理策略。根据你正在实施的测试类型(单元测试、集成测试、部署测试等),你可能需要 embedded 方法、managed 方法或 remote 方法。
Embedded 要配置 embedded 方法,你只需要实例化以下规则:
import static com.lordofthejars.nosqlunit.elasticsearch.EmbeddedElasticsearch.EmbeddedElasticsearchRuleBuilder.newEmbeddedElasticsearchRule;
@ClassRule
public static final EmbeddedElasticsearch EMBEDDED_ELASTICSEARCH = newEmbeddedElasticsearchRule().build();
默认情况下,Elasticsearch Node 以属性 local 设置为 true 启动,但此属性和所有其他支持的属性都可以配置,或者你仍然可以使用 Elasticsearch 提供的默认配置方法,通过在 classpath 中创建 elasticsearch.yml 文件。
数据将存储在 target/elasticsearch-test-data/impermanent-db 目录中。
Managed 要配置 managed 方式,我们应该使用 ManagedElasticsearch 规则,可能需要一些配置参数。
import static com.lordofthejars.nosqlunit.elasticsearch.ManagedElasticsearch.ManagedElasticsearchRuleBuilder.newManagedElasticsearchRule;
@ClassRule
public static final ManagedElasticsearch MANAGED_ELASTICSEARCH = newManagedElasticsearchRule().elasticsearchPath("/opt/elasticsearch-0.20.5" ).build();
默认情况下,托管 Elasticsearch 规则使用以下默认值:
Elasticsearch 安装目录从 ES_HOME 系统环境变量中检索。
Target path, 即启动 Elasticsearch 服务器的目录 target/elasticsearch-temp。
ManagedElasticsearch 可以从头创建,但为了简化生活,提供了使用 ManagedElasticsearchRuleBuilder 类的 DSL ,如前面的示例所示。
Remote 配置 remote 方法不需要特殊的规则,因为您(或像 Maven 这样的系统)负责启动和停止服务器。这种模式用于部署测试,你在真实环境中测试应用程序。
配置 Elasticsearch 连接 下一步是配置 Elasticsearch 规则,负责通过插入和删除定义的数据集将 Elasticsearch 数据库保持在已知状态。你必须注册 ElasticsearchRule JUnit 规则类,这需要包含主机、端口或数据库名称等信息的配置参数。
为了使开发者的生活更轻松并使代码更具可读性,可以使用流式接口来创建这些配置对象。存在三种不同类型的配置构建器。
Embedded 第一种是用于配置到嵌入式 Node 实例的连接。对于几乎所有情况,默认参数就足够了。
import static com.lordofthejars.nosqlunit.elasticsearch.ElasticsearchRule.ElasticsearchRuleBuilder.newElasticsearchRule;
@Rule
public ElasticsearchRule elasticsearchRule = newElasticsearchRule().defaultEmbeddedElasticsearch();
如果你需要自定义嵌入式连接,我们可以使用 EmbeddedElasticsearchConfigurationBuilder 类构建器。
Managed 第二种是用于配置到托管/远程 Elasticsearch 服务器的连接。默认值为:
Host: localhost
Port: 9300
import static com.lordofthejars.nosqlunit.elasticsearch.ElasticsearchRule.ElasticsearchRuleBuilder.newElasticsearchRule;
@Rule
public ElasticsearchRule elasticsearchRule = newElasticsearchRule().defaultManagedElasticsearch();
但你可以通过使用 ManagedElasticsearchConfigurationBuilder 类来自定义连接参数。在那里你可以设置 Settings 类并定义传输端口。
Remote 此外,我们还可以使用 RemoteElasticsearchConfigurationBuilder ,它允许我们设置主机。
@Rule
public ElasticsearchRule elasticsearchRule = newElasticsearchRule().configure(remoteElasticsearch().host("10.0.10.1" ).build()).build();
完整示例 {
"documents" : [
{
"document" : [
{
"index" : {
"indexName" : "books" ,
"indexType" : "book" ,
"indexId" : "1"
}
} ,
{
"data" : {
"title" : "The Hobbit" ,
"numberOfPages" : 293
}
}
]
}
]
}
@ClassRule
public static final ManagedElasticsearch MANAGED_EALSTICSEARCH = newManagedElasticsearchRule().elasticsearchPath("/opt/elasticsearch-0.20.5" ).build();
@Rule
public ElasticsearchRule elasticsearchRule = newElasticsearchRule().defaultManagedElasticsearch();
@Inject
private Client client;
@Test
@UsingDataSet(locations="books.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void all_books_should_be_returned () {
BookManager bookManager = new BookManager (client);
List<Book> books = bookManager.searchAllBooks();
assertThat(books, hasItems(new Book ("The Hobbit" , 293 )));
}
高级用法
自定义插入和比较策略 NoSQLUnit 提供默认的数据集格式,例如在 Neo4j 的情况下,我们提供 GraphML 格式,或者在 Cassandra 的情况下,我们提供 cassandra-unit 格式。但是因为你可能已经用另一种格式编写了数据集,或者因为你更喜欢另一种格式,NoSQLUnit 提供了一种扩展插入和比较行为的方法。
要创建扩展,每个引擎都提供两个接口(一个用于插入,一个用于比较)。它们的调用形式为:ComparisonStrategy 和 InsertionStrategy。例如 CassandraComparisonStrategy 或 Neo4jInsertionStrategy。
它们提供一种方法,用于将输入流中定义的数据集文件插入/比较数据,并提供一个回调接口,包含所有连接对象。
除此之外,每个引擎都有一个默认实现 DefaultComparisonStrategy 和 DefaultInsertionStrategy,可用作开发自己扩展的指南。
要注册每个策略,我们必须使用 @CustomInsertionStrategy 和 @CustomComparisonStrategy 注解。
让我们看一个简单的示例,我们使用 properties 文件而不是 json 定义 Redis 系统的替代插入策略。
public class PropertiesCustomInsertion implements RedisInsertionStrategy {
@Override
public void insert (RedisConnectionCallback connection, InputStream dataset) throws Throwable {
Properties properties = new Properties ();
properties.load(dataset);
BinaryJedisCommands insertionJedis = connection.insertionJedis();
Set<Entry<Object, Object>> entrySet = properties.entrySet();
for (Entry<Object, Object> entry : entrySet){
String key = (String) entry.getKey();
String value = (String) entry.getValue();
insertionJedis.set(key.getBytes(), value.getBytes());
}
}
}
@CustomInsertionStrategy(insertionStrategy = PropertiesCustomInsertion.class)
public class WhenPropertiesCustomInsertionStrategyIsRegistered {
@ClassRule
public static EmbeddedRedis embeddedRedis = newEmbeddedRedisRule().build();
@Rule
public RedisRule redisRule = newRedisRule().defaultEmbeddedRedis();
@Inject
public Jedis jedis;
@Test
@UsingDataSet(locations="data.properties", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void data_should_be_inserted_from_properties_file () {
String name = jedis.get("name" );
String surname = jedis.get("surname" );
assertThat(name, is("alex" ));
assertThat(surname, is("soto" ));
}
}
警告
自定义注解仅在类型作用域有效。自定义策略将应用于整个测试。
当使用自定义策略插入和比较数据时,@UsingDataSet 和 @ShouldMatchDataSet 的 location 属性必须指定。
嵌入式内存 Redis 当你编写单元测试时,你应该记住它们必须运行得快,这意味着,除其他外,不与 IO 子系统(磁盘、网络等)交互。为了避免在数据库单元测试中进行这种交互,有嵌入式内存数据库,如 H2 , HSQLDB , Derby 或在 NoSQL 方面,像 Neo4j 或 Cassandra 这样的引擎有自己的实现。但是 Redis 没有任何方法在 Java 中创建嵌入式内存实例。因此,我基于 Jedis 项目编写了一个嵌入式内存 Redis 实现。
如果你使用 NoSQLUnit ,你只需要按照上述方式注册嵌入式 Redis 规则,内部NoSQLUnit 将为你创建实例,你将能够在代码中使用该实例。
但它也可以在 NoSQLUnit 之外使用,通过手动实例化,如下所述:
EmbeddedRedisBuilder embeddedRedisBuilder = new EmbeddedRedisBuilder ();
Jedis jedis = embeddedRedisBuilder.createEmbeddedJedis();
注意,Jedis 类是 Jedis 项目定义的主要类,但代理为使用内存数据而不是向远程服务器发送请求。
几乎所有 Redis 操作都已实现,但有一些限制:
连接命令什么都不做,它们不抛出任何异常,也不执行任何操作。事实上,这样做没有意义。
脚本命令不受支持,如果调用它们将抛出 UnsupportedOperationException。
事务命令不受支持,但它们不抛出异常,只是返回 null 值,在 List 返回类型的情况下,返回空列表。
Pub/Sub 命令什么都不做。
服务器命令已实现,但有些命令没有意义并返回恒定结果:
move 总是返回 1。
debug 命令抛出 UnsupportedOperationException。
bgrewriteaof, save, bgsave, configSet, configResetStat, salveOf, slaveOfNone and slowLogReset 返回 OK。
configGet, slowLogGet and slowLogGetBinary 返回空列表。
从 Key 命令中,只有 sort by pattern 不支持。
所有其他操作,包括刷新、过期控制以及每种数据类型的每个操作都以与 Jedis 支持相同的方式实现。注意,过期管理也按照 Redis 手册中描述的那样实现。
警告
此 Redis 实现仅供测试使用,不作为 Redis 的替代品。如有任何问题,请随时通知,以便修复或实施。
管理多个实例的生命周期 有时你的测试可能需要启动同一数据库服务器的多个实例(运行在不同端口)。例如用于测试数据库分片。我们在前面看到如何配置NoSQLUnit 来管理多个实例的生命周期。
@ClassRule
public static ManagedRedis managedRedis79 = newManagedRedisRule()
.redisPath("/opt/redis-2.4.16" )
.targetPath("target/redis1" )
.configurationPath(getAbsoluteFilePath("src/test/resources/redis_6379.conf" ))
.port(6379 )
.build();
@ClassRule
public static ManagedRedis managedRedis80 = newManagedRedisRule()
.redisPath("/opt/redis-2.4.16" )
.targetPath("target/redis2" )
.configurationPath(getAbsoluteFilePath("src/test/resources/redis_6380.conf" ))
.port(6380 )
.build();
警告
注意,目标路径应设置为每个实例的不同值,否则一些启动进程可能无法关闭。
快速方式 当你实例化一个规则以保持数据库处于已知状态(MongoDbRule, Neo4jRule, ...)时,NoSQLUnit 需要你设置一个包含主机、端口、数据库名称等属性的配置对象,但尽管大多数时候默认值就足够了,我们仍然需要创建配置对象,这意味着我们的代码变得更难读。
我们可以通过使用每个规则内部的构建器来避免这一点,它为我们创建了一个带有默认参数设置的规则。例如对于 Neo4jRule:
import static com.lordofthejars.nosqlunit.neo4j.Neo4jRule.Neo4jRuleBuilder.newNeo4jRule;
@Rule
public Neo4jRule neo4jRule = newNeo4jRule().defaultEmbeddedNeo4j();
在前面的示例中,Neo4jRule 配置为使用默认参数作为嵌入式方法。
另一个使用 CassandraRule 的托管方式的示例。
import static com.lordofthejars.nosqlunit.cassandra.CassandraRule.CassandraRuleBuilder.newCassandraRule;
@Rule
public CassandraRule cassandraRule = newCassandraRule().defaultManagedCassandra("Test Cluster" );
同时引擎 有时应用程序将包含多个 NoSQL 引擎,例如你模型的某些部分最好表示为图(例如 Neo4J),但其他部分在列方式下更自然(例如使用 Cassandra)。NoSQLUnit 通过提供在集成测试中不将所有数据集加载到一个系统中的方式来支持这类场景,而是选择将哪些数据集存储在每个后端。
要声明多个引擎,你必须使用 connectionIdentifier() 方法在配置实例中给每个数据库 Rule 命名。
@Rule
public MongoDbRule remoteMongoDbRule1 = new MongoDbRule (mongoDb()
.databaseName("test" ).connectionIdentifier("one" ).build(),this );
你还需要为每个引擎提供标识的数据集,通过使用 @UsingDataSet 注解的 withSelectiveLocations 属性。你必须设置'命名连接'/数据集的对。
@UsingDataSet(withSelectiveLocations = {
@Selective(identifier = "one", locations = "test3")
}, loadStrategy = LoadStrategyEnum.REFRESH)
在上面的示例中,我们用位于 test3 文件的数据刷新上面声明的数据库。
@ShouldMatchDataSet(withSelectiveMatcher = {
@SelectiveMatcher(identifier = "one", location = "test3")
})
如果设置了 location 属性,它将使用它并忽略 withSelectiveMatcher 属性数据。Location 数据通过所有注册的系统进行填充。
如果未设置 location,则系统尝试将 withSelectiveMatcher 属性中定义的数据插入到每个后端。
如果未设置 withSelectiveMatcher 属性,则采用默认策略(在章节中解释)。注意,默认策略将复制所有数据集到定义的引擎。
你也可以使用相同的方法将数据插入到同一引擎但在不同的数据库中。如果你有一个 MongoDb 实例和两个数据库,你也可以同时为两个数据库编写测试。例如:
@Rule
public MongoDbRule remoteMongoDbRule1 = new MongoDbRule (mongoDb()
.databaseName("test" ).connectionIdentifier("one" ).build(),this );
@Rule
public MongoDbRule remoteMongoDbRule2 = new MongoDbRule (mongoDb()
.databaseName("test2" ).connectionIdentifier("two" ).build(),this );
@Test
@UsingDataSet(withSelectiveLocations = {
@Selective(identifier = "one", locations = "json.test"),
@Selective(identifier = "two", locations = "json3.test")
}, loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
public void my_test () {...}
JSR-330 支持 NoSQLUnit 支持 JSR-330 aka Dependency Injection for Java 的两个注解。具体是 @Inject 和 @Named 注解。
在测试执行期间,你可能需要访问用于加载和断言数据的底层类,以对后端执行额外操作。NoSQLUnit 将检查测试字段的 @Inject 注解,并尝试将自身的驱动程序设置到属性。例如在 MongoDb 的情况下,com.mongodb.Mongo 实例将被注入。
@Rule
public MongoDbRule remoteMongoDbRule1 = new MongoDbRule (mongoDb()
.databaseName("test" ).build(),this );
@Inject
private Mongo mongo;
警告
注意,在上面我们设置了 this 作为规则的第二个参数。这仅在 JUnit 4.11 之前的版本中需要。在新版本中不再需要传递 this 参数。
但是如果你在同一时间使用多个引擎(参见前面),你需要一种方法来区分每个连接。为了解决这个问题,你必须使用 @Named 注解,将配置实例中给出的标识符放在那里。例如:
@Rule
public MongoDbRule remoteMongoDbRule1 = new MongoDbRule (mongoDb()
.databaseName("test" ).connectionIdentifier("one" ).build(),this );
@Rule
public MongoDbRule remoteMongoDbRule2 = new MongoDbRule (mongoDb()
.databaseName("test2" ).connectionIdentifier("two" ).build(),this );
@Named("one")
@Inject
private Mongo mongo1;
@Named("two")
@Inject
private Mongo mongo2;
有一些情况(主要是如果使用 Arquillian ),你想要注入容器管理的值而不是 NoSQLUnit 管理的值。为了避免注入冲突,NoSQLUnit 提供了一个特殊的注解 @ByContainer 。使用它,注入处理器将留字段 untouched。
@Inject
@ByContainer
private Mongo mongo2;
Spring Data 使用 NoSQLUnit ,你也可以为 Spring Data 项目编写测试。你可以无缝地将 NoSQLUnit 集成到 Spring Data 项目中,利用其现有的依赖注入机制。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online