Nowadays the "right tool for the right job" idea became quite popular, and I strongly agree with that. You can always argue about the state-of-art tool and I think such discussion is healthy, but believing a special technology shall solve all your problems is naive. Experienced developers should be open enough to accept the fact
there will be never a silver bullet.
In the past weeks I have been looking for a replacement for
Maven as my default build system, then I have naturally stumbled on
Gradle. Rather than making use of XML, Gradle defines a Groovy-based internal DSL which is really easy to use. Unfortunately I had to drop it for now because its Scala plugin
will be released only within version 0.8, so I ended up with
Apache Buildr. Buildr is Ruby all the way down, and it already includes support for Scala, Groovy and a growing number of JVM languages.
In a new experiment I am working on, I have decided to go ahead with Java and Scala for production code and Groovy for testing. Besides the learning part, that is mostly due to
an evaluation I have done some time ago involving JVM languages.
Below how my project looks like:
- myapp/
+ buildfile
- module1/
- src/
- main/
- java/
- scala/
- myapp/
- module1/
- account/
+ Account.scala
+ AccountService.scala
+ AccountServiceImpl.scala
- spec/
- groovy/
- myapp/
- module1/
- account/
+ CompareAccountsBehavior.groovy
+ ManageAccountsBehavior.groovy
+ SignInBehavior.groovy
1 - Build scriptNothing special here, just concise Ruby code defining the build and putting Java, Scala and Groovy in the loop. For testing I am using
EasyB, a behavior driven development framework for Java which allows to write specifications in Groovy.
require 'buildr'
require 'buildr/groovy'
require 'buildr/java'
require 'buildr/scala'
repositories.remote << 'http://www.ibiblio.org/maven2'
COMMONS = 'commons-lang:commons-lang:jar:2.4'
define 'myapp' do
project.group = 'module1'
project.version = '1.0-SNAPSHOT'
manifest['Author'] = 'Tiago Fernandez'
define 'module1' do
compile.with COMMONS
test.using :easyb
package(:jar)
end
end
2 - SpecificationsThe best part of BDD is the fact that specs and stories get pretty readable and self-explanatory, thus any comment I might do will end up being redundant.
CompareAccountsBehavior.groovy:
package myapp.module1.account
final accountId = 1
before "create two accounts with the same id", {
firstAccount = new Account(id: accountId)
secondAccount = new Account(id: accountId)
}
it "should make sure both accounts with the same id are the same", {
firstAccount.shouldBeEqualTo secondAccount
}
it "should make sure both accounts with the same id have the same hash code", {
firstAccount.hashCode().shouldBeEqualTo secondAccount.hashCode()
}
ManageAccountsBehavior.groovy
package myapp.module1.account
final accountSvc = new AccountServiceImpl()
it "should create a new account", {
username = 'tiago'; password = 'secret'
accountSvc.isUsernameInUse(username).shouldBe false
accountSvc.createAccount new Account(username: username, password: password)
accountSvc.isUsernameInUse(username).shouldBe true
}
it "should not create a new account with blank username", {
ensureThrows(RuntimeException) {
accountSvc.createAccount new Account(username: ' ', password: 'foo')
}
}
it "should not create a new account with blank password", {
ensureThrows(RuntimeException) {
accountSvc.createAccount new Account(username: 'foo', password: ' ')
}
}
it "should not create a new account with blank username and password", {
ensureThrows(RuntimeException) {
accountSvc.createAccount new Account(username: ' ', password: ' ')
}
}
SignInBehavior.groovy:
package myapp.module1.account
final accountSvc = new AccountServiceImpl()
it "should sign an existing user in", {
account = new Account(id: 1, username: 'tiago', password: 'secret')
accountSvc.createAccount account
accountSvc.signIn(account.username, account.password).get().shouldBe account
}
it "should not allow non-existing users to sign in", {
accountSvc.signIn('foo', 'bar').isEmpty().shouldBe true
}
3 - Production codeHere I use only Scala for now, but I plan to go ahead with Java as well when appropriated. Below the code drove by specifications:
Account.scala:
package myapp.module1.account
import java.io._
import org.apache.commons.lang.builder._
class Account extends Serializable {
var id: Int = _
var username: String = _
var password: String = _
override def equals(other: Any): boolean = {
val otherAccount = other.asInstanceOf[Account]
new EqualsBuilder()
.append(this.id, otherAccount.id)
.isEquals()
}
override def hashCode(): int = {
new HashCodeBuilder()
.append(id)
.toHashCode()
}
}
AccountService.scala:
package myapp.module1.account
trait AccountService {
def createAccount(account: Account): Account
def isUsernameInUse(username: String): boolean
def signIn(username: String, password: String): Option[Account]
}
AccountServiceImpl.scala:
package myapp.module1.account
import java.util._
import org.apache.commons.lang._
class AccountServiceImpl() extends AccountService {
val existingUsers = new HashMap[String, Account]
def createAccount(account: Account): Account = {
require(StringUtils.isNotBlank(account.username))
require(StringUtils.isNotBlank(account.password))
existingUsers.put(account.username, account)
}
def isUsernameInUse(username: String): boolean = {
existingUsers.containsKey(username)
}
def signIn(username: String, password: String): Option[Account] = {
var signedInUser: Option[Account] = None
if (isUsernameInUse(username)) {
val account = existingUsers.get(username)
if (passwordsMatch(account, password)) signedInUser = Some(account)
}
signedInUser
}
private def passwordsMatch(account: Account, password: String): boolean = {
password.equals(account.password)
}
}
I hope this quickstart project can be useful for someone rather than me.
Note: Since I am still a newbie to Scala, any suggestions for improving this code are welcome :)