有足够的证据证明自动化测试的重要性。 新领域的项目通常会忽略自动化测试,因为领域本身会吸引开发人员的注意力。 但是,缺乏测试意味着“现在就笑,晚点哭”。 大数据空间中的一些工具是围绕可测试性设计的,至少在此之后,社区对此进行了照顾。 我们将看到Spark (尤其是Spark Streaming )在自动化测试的不同方面的表现。
什么是流处理
流处理是一种编程范例,适用于无限和连续的数据流,并对它们应用并行操作。 这个想法很简单,但是功能强大,实现的复杂性将根据以下要求而有所不同:
- 语义传递:至少一次,最多一次或恰好一次。
- 有状态操作:本地或远程状态。
- 延迟:实时或接近实时。
- 可靠性,高可用性和耐用性。
什么是Spark Streaming
Spark是大数据领域的一场革命。 它取代了Hadoop的MapReduce ,成为首选的批处理框架。 主要原因是:
- 速度: 在内存中运行程序的速度比Hadoop MapReduce快100倍,而在磁盘上的速度快10倍。
- 可用性:MapReduce DSL远非易写和易读。 Spark Scala DSL是Scala Collections操作的扩展,因此学习曲线并不陡峭。
- 社区:Spark周围有很多令人兴奋的地方,并且有很多相关工具,例如MLib,Spark SQL或Spark Streaming。
Spark Streaming构建在Spark之上。 这意味着您可以使用Spark基础结构和概念,例如YARN , HDFS或RDD 。 最重要的是,我们将提供抽象来帮助我们构建流功能,例如聚合或滑动窗口。
什么是单元测试
这是一个关于单元测试的不同观点的精彩系列。 为了使本文的重点突出,我们将使用以下特征:
- 网络隔离:测试中的生产代码将涉及驻留在单个进程中的代码。 不允许网络通话。
- 框架隔离:我们想尽可能地测试我们的代码,而不是与底层框架的交互。
火花测试营救
控制Spark的生命周期可能很繁琐且乏味。 幸运的是, Spark Testing Base项目为我们提供了Scala特性,可为我们处理这些低级细节。 由于我们需要及时生成数据以进行提取,因此流式传输具有额外的复杂性。 同时,如果我们想将计时操作作为滑动窗口进行测试,Spark内部时钟需要以可控的方式计时。
让我们看看如何测试WordCount的原型示例:
def count(lines: DStream[String]): DStream[(String, Int)] =
lines.flatMap(_.split(" "))
.map(word => (word, 1))
.reduceByKey(_ + _)
如您所见,这是一个纯函数,没有副作用或访问外部状态。 我们可以通过查看函数的签名来进行推理。 DStream是Spark Streaming中的基本抽象,Spark Testing Base将帮助我们处理它。
class WordCountSpec extends StreamingSuiteBase {
test("count words") {
val input = List(List("the word the"))
val expected = List(List(("the", 2), ("word", 1)))
testOperation[String, (String, Int)](input, count _ , expected, ordered = false)
}
}
您不需要直接使用DStream抽象。 输入将是一系列输入集合,并且每个集合都会消耗一点点Spark Streaming内部时钟。 您可以在此处找到有关如何使用此库的更多示例。
连接流和批处理
流处理中的一种经典方案是将流与数据库结合在一起,以丰富,过滤或转换流中包含的事件。 多亏了Spark 2.0和结构化流技术 ,流和批处理在抽象层中对齐并隐藏了。
随着Spark 2.0的最新发布,让我们关注旧API的示例:
def countWithSpecialWords(lines: DStream[String], specialWords: RDD[String]):
DStream[(String, Int)] = {
val words = lines.flatMap(_.split(" "))
val bonusWords = words.transform(_.intersection(specialWords))
words.union(bonusWords)
.map(word => (word, 1))
.reduceByKey(_ + _)
}
这是一个令人费解的示例,但仅作为示例。 我们的系统在外部数据库中保留特殊词的列表。 我们希望在该特殊单词袋中包含的流中对单词进行两次计数。 重要的是要注意,我们的函数无需担心如何检索那些特殊的单词。 这是在函数之外完成的,这使我们有机会对逻辑进行单元测试。
val lines = ingestEventsFromKafka(ssc, brokers, topic).map(_._2)
val specialWords = ssc.sparkContext
.cassandraTable(keyspace, specialWordsTable)
.map(_.getString("word"))
countWithSpecialWords(lines, specialWords)
.saveToCassandra(keyspace, wordCountTable)
目前在Spark Testing Base上不支持这种操作,但是我已经创建了可以提供该功能的PR 。
test("stream and batch transformation") {
def intersection(f1: DStream[String], f2: RDD[String]) = f1.transform(_.intersection(f2))
val stream = List(List("hi"), List("holden"), List("bye"))
val batch = List("holden")
val expected = List(List(), List("holden"), List())
testOperationWithRDD[String, String, String](stream, batch, intersection _, expected, ordered = false)
}
结论
借助Spark Testing Base,单元测试Spark Streaming非常容易。 但是,如果要利用此库,我们需要干净地设计我们的操作。 在接下来的文章中,我们将看到如何使用Spark Streaming,Kafka和Cassandra进行集成测试。
翻译自: https://www.javacodegeeks.com/2016/08/testing-spark-streaming-unit-testing.html