I've read a good bit about ScalaTest and BDD before, but I actually used it for the first time on a particular project I'm working on at the moment.
The code in question was a bit of utility code that simply extracts a zip archive or archive's a directory (a basic packaging utility). Writing a spec for this was very easy.
- Given an archive, extracting it should produce the known directory structure.
- The extracted contents should be the same as the known contents
- Archiving should produce an archive of directory
- Archive should be valid (ie: no errors on extracting)
That's four quick ones. Below, I have the spec written out as a ScalaTest. I couldn't help but to have a big, goofy smile when I ran it through sbt.
package test
import org.scalatest.{FlatSpec, BeforeAndAfterAll}
import org.scalatest.matchers.ShouldMatchers
import Zip._
class ZipSpec extends FlatSpec with ShouldMatchers with BeforeAndAfterAll {
import java.io.File
val archivePath = getClass.getClassLoader.getResource("archive.zip")
override def afterAll(configMap: Map[String, Any]) {
def recurse(file: File)(fun: File => Unit) {
if(file.isDirectory)
file.listFiles.filter(f => !f.getName.startsWith(".")).foreach {
recurse(_)(fun)
}
fun(file)
}
// Delete temp files
recurse(new File("archive")) { _.delete }
recurse(new File("temp")) { _.delete }
new File("../archive.zip").delete
new File("archive.zip").delete
}
"Test archive" should "exists" in {
val archive = new File(archivePath.getFile)
archive should not be (null)
}
"Extract" should """create directory tree:
archive/
archive/child/
archive/child/more.txt
archive/test.xml""" in {
extract(archivePath.getFile)
// Checking Dir tree
new File("archive") should be ('exists)
new File("archive/test.xml") should be ('exists)
new File("archive/child") should be ('exists)
new File("archive/child/more.txt") should be ('exists)
}
it should """create a new directory tree:
temp/archive
temp/archive/child/
temp/archive/child/more.txt
temp/archive/test.xml""" in {
extract(archivePath.getFile, "temp")
new File("temp/archive") should be ('exists)
new File("temp/archive/test.xml") should be ('exists)
new File("temp/archive/child") should be ('exists)
new File("temp/archive/child/more.txt") should be ('exists)
}
"Extracted flat files" should "contain correct data" in {
import scala.xml._
import scala.io.Source.{fromFile => open}
val xmltext = <stuff>
<more-stuff>Test</more-stuff>
</stuff>
val text = "You'll never find me!"
XML.loadFile("archive/test.xml") should be === xmltext
open("archive/child/more.txt").getLines.mkString should be === text
}
"Archive" should "create archive.zip" in {
archive("archive")
new File("archive.zip") should be ('exists)
}
it should "create archive.zip in parent directory" in {
archive("archive", "../")
new File("../archive.zip") should be ('exists)
}
"../archive.zip and archive.zip" should "be the same size" in {
val parentzip = new File("../archive.zip")
val archivezip = new File("archive.zip")
parentzip.length should be === archivezip.length
}
"Extracting archive.zip" should "not throw any exceptions" in {
extract("archive.zip")
}
}
Here's example output from sbt's execution of the test spec.
[info] == test.ZipSpec == [info] Test archive [info] Test Starting: Test archive should exists [info] Test Passed: Test archive should exists [info] Extract [info] Test Starting: Extract should create directory tree: [info] archive/ [info] archive/child/ [info] archive/child/more.txt [info] archive/test.xml [info] Test Passed: Extract should create directory tree: [info] archive/ [info] archive/child/ [info] archive/child/more.txt [info] archive/test.xml [info] Test Starting: Extract should create a new directory tree: [info] temp/archive [info] temp/archive/child/ [info] temp/archive/child/more.txt [info] temp/archive/test.xml [info] Test Passed: Extract should create a new directory tree: [info] temp/archive [info] temp/archive/child/ [info] temp/archive/child/more.txt [info] temp/archive/test.xml [info] Extracted flat files [info] Test Starting: Extracted flat files should contain correct data [info] Test Passed: Extracted flat files should contain correct data [info] Archive [info] Test Starting: Archive should create archive.zip [info] Test Passed: Archive should create archive.zip [info] Test Starting: Archive should create archive.zip in parent directory [info] Test Passed: Archive should create archive.zip in parent directory [info] ../archive.zip and archive.zip [info] Test Starting: ../archive.zip and archive.zip should be the same size [info] Test Passed: ../archive.zip and archive.zip should be the same size [info] Extracting archive.zip [info] Test Starting: Extracting archive.zip should not throw any exceptions [info] Test Passed: Extracting archive.zip should not throw any exceptions [info] == test.ZipSpec == [info] [info] == test-complete == [info] == test-complete == [info] [info] == test-finish == [info] Passed: : Total 8, Failed 0, Errors 0, Passed 8, Skipped 0 [info] [info] All tests PASSED. [info] == test-finish ==
As inevitably a part of testing an archive utility, the test code will create a bunch of files we don't want. Fortunately for us, ScalaTest comes with a BeforeAndAfterAll trait for us. I just to needed override afterAll in this case, where I put the code to remove what was created.
I feel the immediate advantage of this test framework is that the test code itself quite literally explains what's going on. Once I got the spec written, refactoring the implementation code was breezy (which is characteristic of unit testing in general).
Consider me a ScalaTest convert. I doubt I'll ever write a traditional unit test again.
-- Philip
No comments:
Post a Comment