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