sbt 任务间的依赖

项目中的 build.sbt 中发现定义任务时有 task2 <<= task1 map {...} 这样任务依赖的写法, 这个  <<= 方法有点晦涩难懂, 读过 sbt in action 之后才知道这是 sbt 0.12 或之前的做法, sbt 0.13 之后不这么用了, 直接访问下 task1.value 就行, 因此前面可改写为 task2 := {task1.value; ...}, 这也使得定义任务依赖时与普通任务一致风格了. 新的写法得益于 Scala 2.10 的宏特性, 后面还会讲到.

对于依赖于多个任务的情况, 在 sbt 0.13 前面分别是这样的, 假定有三个 Task(task1, task2, 和 task3)

val task1 = taskKey[Int]("task 1")
val task2 = taskKey[Int]("task 2")
val task3 = taskKey[Int]("task 3")

如果 task3 依赖于 task1 和 task 2

sbt 0.13 前后版本中的写法分别是

像任务的 .value 属性其实是一个宏定义, 源码 阅读全文 >>

ScalaTest + Selenium 集成测试

在 Play 1 和 Play 2 中都内置了 Selenium 集成测试工具, 这里自己尝试自己单独测试用 ScalaTest + Selenium 来做简单的集成测试. Selenium 可以支持内置的无界面 Java 实现的浏览器, 也可以用外部浏览器, 如 Safari, Firefox, Chrome, IE, Opera 或移动设备的浏览器. 使用不同外部浏览的方式或用插件(Safari/Firefox 等) 或是像借助于 Chromium 来驾驭 Chrome 浏览器.

而我们这里要用的 ScalaTest 2.2.6 在包 org.scalatest.selenium 有以下几个特质 Chrome, Driver, Firefox, HtmlUnit, InternetExplorer, Page, Safari 和  WebBrowser. 由此可以看出 ScalaTest 默认支持的浏览器. 若单论 Selenium 本身, 它可强大的暂时超乎我的想像, 可以自建服务器, 选择浏览器分发测试任务. 我们知道使用 Selenium 的好处是不光可以像通常那样断言页面静态文本, 还能执行 Javascript 脚本, 所以可断言动态内容.

本人开发环境为 Mac OS, 可以成功让 Selenium 测试跑在内置 Java 浏览器, 和外置的  Safari, Chrome 浏览器中, Firefox 的插件未安装成功.

让事实说话, 仍然让一个最简单的例子自己说话, 创建的是一个 sbt 项目, 尽量把目录最简化, 只有 build.sbt 文件和 test/IntegrationTest.scala 测试代码, 内容分别为 阅读全文 >>

建立 Play 2 框架一样的目录布局

sbt 项目继承并扩展了 Maven 的默认项目布局, 加入了 Scala 代码的支持, 所以目录如 Shell 命令 mkdir -p src/{main,test}/{java,scala,resources} 生成的目录结构, 即

.
└── src
    ├── main
    │   ├── java
    │   ├── resources
    │   └── scala
    └── test
        ├── java
        ├── resources
        └── scala

这个目录目录虽然很清晰, 但把 Java 和 Scala 代码拆在两处没多大必要, 其次是层次多了点. 因使用 Play Framework 时日有点久了, 比较习惯于 Play 2 改造后的项目布局. 我们启动到 Play 2 的 activator(其实就是加入了定制的 sbt) 控制台, 用命令看它的目录布局 阅读全文 >>

sbt 中单元测试并发执行

此次研究的目的原本是要使得 Play Framwork 2 中单元测试能够并发执行, 包括 JUnit 和 Spec 的测试用例, Play 2 的 activator 就是一个 sbt 的包装. 开发中发现我们 Play 2 中的单元测试是按序执行的, 实际上 sbt 下测试用例默认是并发执行的. 之所以 Play 2 的单元测试是按序的, 是因为 activator 设置了把 sbt 的两个属性 fork in Test := trueparallelExecution in Test := false, 见 PlaySettings.scala, 它们默认分别为 false 和 true. 这使得默认设置下 Play 2 中的所有测试无法并发执行.

sbt 默认的 fork 是 false, Play 2 改为 true 之后便可以使用 javaOptions in Test := "-Dkey1=value1" (注: 如果 fork 为 false 的话, javaOptions 将无效.) 往单元测试中参数了, 这也是为什么在 Play 2 的单元测试中无法获得启动 sbt 时(像 sbt -Dkey1=value) 的参数, 不同一个 JVM 啊.

那是不把 Play 2 的 fork in TestparallelExecution in Test 分别改回成 false 和 true 就可以让测试用例并发执行了呢? 答案是 Yes. 但我们得相信 Play 2 把它们预设为 true 和 false 是有它的用意的, 比如集成测试的每个用例都会开启本地的 3333 端口, 如果让两个集成测试同时执行将会造成端口冲突. 细致说来, Play 2 其实是懒政, 只管一刀切而让所有测试按序执行而影响了效率, 如能利用好 sbt 的测试分组机制是可以达到测试的并发执行的.

这里引出 sbt 执行测试的几个机制:

1) sbt 总是对测试进行分组, 默认时所有的测试都包含在 <default> 组中, 可用 show testGrouping 查看, 如

阅读全文 >>

查看 sbt 项目的依赖关系树

sbt 是借助于 ivy 来管理项目依赖, 像 Maven 项目中可以用 dependency:tree 来显示依赖树, 那么对于 sbt 项目该如何查看项目依赖关系呢? 本文提及了三种方式来显示项目依赖, 它们是 Shell 脚本, 自定义 sbt 任务, 和 sbt-dependency-plugin 方式. 最后一个办法使得我们也能用 dependencyTree 显示出 Maven 的  dependency:tree 效果来, 还有更酷的的.

> dependencyTree
[info] default:test_2.10:0.1-SNAPSHOT [S]
[info]   +-ch.qos.logback:logback-classic:1.0.13
[info]     +-ch.qos.logback:logback-core:1.0.13
[info]     +-org.slf4j:slf4j-api:1.7.5
[info]
[success] Total time: 0 s, completed Apr 5, 2016 12:29:53 AM

下面是探索的全部过程.

通过 sbt 控制台的 tab  自动完成或用 help .*[Dd]ependenc.* 命令再进一步过滤出与依赖比较接近 sbt 控制台任务 阅读全文 >>

Java 构建工具及 sbt 最快速体验

应对 Java 项目, 我们大概有以下几个自动化构建工具:

  1. Ant -- XML 化跨平台批处理, 配置文件 build.xml, 执行的是 target
  2. Maven -- 开始标准化目录布局, 基于项目对象模型, 配置文件 pom.xml, 执行的是 phase/goal
  3. Gradle  -- 使用 Maven 默认布局, Groove 语言铸就, 配置是 groovy 语法的 build.gradle 文件, 执行的是 task
  4. Buildr  -- 默认也是 Maven 目录布局, Java 世界被 Ruby 插手, 配置是 ruby 语法的 buildfile 文件, 也是 task
  5. Leiningen -- 也采用 Maven 目录布局, Clojure 写的, 可用于构建 Java 和 Clojure 项目, 配置文件是 Clojure 语法的 prject.clj, 基于 task
  6. sbt  -- 默认 Maven 目录布局, Scala(Simple) Build Tool, Scala 写的, 构建 Java 和 Scala 项目, 配置是 Scala 语法的 build.sbt, 基于 task. 交互式控制台.

Ant 让我们摆脱了对系统平台的依赖, 终于不同人构建的工件是一样的了, 曾经它就是昭示着敏捷. 除了 Ant 需要我们定义所有的 target 外, 其他构建都内置了基本足够用的 task, 而且也都采用了业界接受的 Maven 目录布局. 也是从 Maven 开始引入了项目依赖管理, 所以 Maven 才是里程碑式的.

在我们搜索 Java 第三方依赖时常常进到类似这样的页面 http://mvnrepository.com/artifact/com.google.guava/guava/19.0

java-dependencies 阅读全文 >>

Mac OS X 下安装使用 Docker (新)

两年前的一篇 Mac OS X 下安装使用 Docker 安装时还是用的 boot2docker, 如今进化到了在 Mac OS X 下用 Docker Toolbox, 而且命令也由 boot2docker 换成了 docker-machine. 当然由于是非 Linux 系统, 所以 Mac OS X 仍然需要借助于 VirtualBox 中的 Linux 虚拟机作为桥梁, Docker Toolbox 创建的虚拟机名是 default (boot2docker 创建的虚拟机名是 boot2docker-vm) 就是这一桥梁, 我们称之为 DOCKER_HOST. 文中的 default 虚拟机指的就是这个 DOCKER_HOST.

现在来看下安装步骤及体验, 因为我系统中已安装好了 VirtualBox, 所以这一步骤等会就省略了.

一. 安装 docker 和  docker-machine

官方的指南是通过下载  DockerToolbox 来安装 docker, docker-machine 和其他辅助工具. 但如果你偏于极客, 并不习惯于图形界面来安装的话, 那么安装 docker 和 docker-machine 就只要下面两个指令

如果你已选择了使用上面的两条命令来安装 docker, docker-machine 的话, 那么请跳到下一步.

如果你热爱图形界面来安装应用的话, 可以到页面 https://www.docker.com/products/docker-toolbox 中下载 Mac 的版本的 DockerToolbox-1.10.3.pkg, 当前版本是 1.10.3

Docker Toolbox Installer   Docker Toolbox includings 阅读全文 >>

拆分 Playframework 2 的 routes 为多个文件

我们用 Playframework 2 时,当 routes 中太多的路由配置时,我们可能会考虑把它们归类分布到多个文件中去。比如按 API 或用途分,有些是 RESTful API,有些是 Web 页面的,对于这种情景,我们可以由以下几个文件来组织:

1. routes 文件,这个仍然是充当入口

这里穿插着来解释下,-> 是固定写法,表示要去别外寻找了,紧接着的 /, /api, 和 /web 是分类路由的上下文了,例如,访问 api.routes 中定义的 /customers 的完整 API 路径就是 /test/customers。最后一部分是全类名,并非指别的路由文件的名称,像文件 general.routes 编译后会生成 general 包下生成  Routes.scala 文件,即类为 general.Routes. 阅读全文 >>

如何轻松理解排序函数

这是我自最早接触 Javascript 的排序之日起一直萦绕在脑海中的问题。比如一个简单的 Javascript 数组排序

[3, 2, 4, 6, 5].sort(function(e1, e2) {
    return e1 - e2;
});

当然我那时候还不会用 JSON 和无名函数来写上面的代码。 反正经常是搞不清楚想要升序或降序时,是应该 return e1 - e2 还是 return e2 - e1 ?,没把握就试一下,总是二选一。

现在许多语言对集合排序都是使用排序函数的方式,总不想每次都琢磨不定,每次都去试。需要 TDD 时测试用例必须覆盖排序用例,但还是希望以后书写排序函数时心里有个底。当然,比如在 Java 中对集合进行排序我们可以深入到源代码来理解排序函数是如何影响元素顺序的。

简单通俗点,是否有一种浅显的方式来理解排序函数呢?待我把答案慢慢叙来,就我个人理解就两个原则: 阅读全文 >>

Java 的 fork-join 框架实例备忘

Java 7 首次引入了 fork/join 框架,但一直未曾直接尝试. 而且基本上也很少在实际项目中直接写 fork-join 的代码,在我们使用第三方组件时倒是间接会接触到 fork/join 框架。譬如 Akka 的 fork-join-executor, sbt 执行测试用例时也是默认 fork/join 并发执行。fork-join 可以帮助我们把计算任务粒度细化,并更有效的利用多 CPU 内核。

fork-join 与 map-reduce 有些相妨,在 Java 7 时代我其实是忽视了它的存在。目今正在了解 Java 8 的  parallelStream 时,因为它的底层实现也是 fork/join, 所以有兴致去稍加体验一下。fork/join 的算法简单来讲就是递归对半去细化计算任务,及到不能细化时由多内核(线程)去计算被拆分的任务,最后反方向把结果汇总。

下面是从 《Java 8 IN ACTION》中截的一个说明 fork/join 的处理过程 阅读全文 >>