scala - Assertions and Unit Testing

Scala features by its DSL like assertion and testing, it reads like nartual English and is especially friendly for test specfication writer (they don't need to be some expert in computing or programming language).
There are several Test framework that is available for the Scala programming language.  this include

Scala Test
JUnit and TestNG
Specs
ScalaCheck


Using a assertion

scala has provided base assertion that helps to some test based assertions. the test method that you mihgt use include the following 
  • assert 
  • ensuring 


examples of the scala assertion on the element class and its accompanying object is as follow.

package assertionAndUnitTesting.assertions;
// file:
//   elements.scala
// package: 
//   assertionAndUnitTestting.elements

// description:
//   in this post, we will examine the scala assertions, the very basic construct which allows us to do pre/post condition check 


object Element { 
  
  private class ArrayElement(val contents: Array[String]) extends Element 

  
  private class LineElement(
      s : String) extends Element {
          def contents : Array[String] = Array(s)
          override def height : Int = 1
          override def width : Int = s.length
    }
  
  private class UniformElement(
      chr : Char,
      override val width : Int,
      override val height :Int
      ) extends Element {
    
    // ch * width will fail, 
    // and the error message is not clear - type mismatch (expect String, Int provided) the REPL need to improve 
    // on it error handling.
    private val line : String = chr.toString * width
    override def contents : Array[String] = Array.fill(height)(line)
  }
  
  
  def elem(contents: Array[String]) : Element = new ArrayElement(contents)
  
  def elem(s : String) : Element  = new LineElement(s)
  
  def elem(chr : Char, width : Int, height : Int) : Element= new UniformElement(chr, width, height)
  
}


import Element.elem

// contents: not defined, you need to declare your class as "abstract" 
//  
abstract class Element { 
  def contents : Array[String]
  def height : Int = contents.length
  def width = if (contents.length ==0 ) 0 else contents(0).length
  
  
  def beside(that : Element) : Element = {
    val this1 = this heighten that.height
    val that1 = that heighten this.height
    
    elem (
      for ((line1, line2) <- this1.contents zip that1.contents) yield (line1 + line2)    
    )
    
  }
  
  
  def above(that : Element) : Element = {
    val this1 = this widen that.width
    val that1 = that widen this.width
    
    assert(this1.width == that1.width) // assert that this1.width is equal to that1.width before above is called on the real contents.
    elem(this1.contents ++ that1.contents)
    
  }
  
  def widen(w : Int) : Element = 
    if (w <= width) this
    else {
      val left = elem(' ', (w - width) /2 , height)
      val right = elem(' ', (w - width  + left.width), height)
      
      left beside this beside right
    } ensuring (w <= _.width) // ensure after widen, parameter w is less than the widen one.
  
  def heighten(h : Int) : Element = 
    if (h <= height) this 
    else {
      val top = elem(' ', width, (h - height) / 2)
      val bottom = elem(' ', width, (h - height + top.height))
      
      top above this above bottom
    }
    
  
  override def toString = contents mkString "\n"
    
}


ScalaTest


ScalaTest offers some basic means to write test in Scala and also it provides several bridges to other test framework such as Junit and TestNG..
a very straightfoward test case in ScalaTest is as follow. 

// file:
//   scalatest_testsuite.scala
// package: 
//   assertionAndUnitTesting.testing.scalatests
package assertionAndUnitTesting.testing.scalatests 

import org.scalatest.Suite
// import assertionAndUnitTesting.testing.scalatests
import assertionAndUnitTesting.assertions._
import assertionAndUnitTesting.assertions.Element._

// extends the org.scalatest.Suite and define test method with testXXX names

class ElementSuite extends Suite {
  
  def testUniformElement() { 
    val ele = elem('x', 2, 3 )
    assert(ele.width == 2)
  }
}


and scalatest also provides a different style of testing, it also provide a FunSuite, where you can override the test method and supply the a function value rather than methods. 

// file:
//   scalatest_funsuite.scala
// package: 
//   assertionAndUnitTesting.testing.scalatests
package assertionAndUnitTesting.testing.scalatests 

import org.scalatest.FunSuite 
import assertionAndUnitTesting.assertions._
import assertionAndUnitTesting.assertions.Element._

// test is passed in as a by-name parameter to the test method 
class ElementSuite extends FunSuite { 
  test("elem result should have passed width") { 
    val ele = elem('x', 2, 3)
    assert(ele.width == 2)
  }
}



in this post, we will going to examine some of the common used test framework and show some guidelines on how to write Unit Test for Scala programming language. 

Using Junit and TestNG

to use the JUnit, the following example shows that you can run junit scala test code with the Junit test runner. 

// file 
//  junits_testcase.scala
package assertionAndUnitTesting.testing.junits


import junit.framework.TestCase
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
import assertionAndUnitTesting.assertions.Element.elem;


class ElementTestCase extends TestCase { 
  def testUnitformElement()  {
    val ele = elem('x', 2, 3)
    assertEquals(2, ele.width)
    assertEquals(3, ele.height)
    try { 
      elem('x', -2, 3)
      fail()
      
    }
    catch {
      case e : IllegalArgumentException =>  {
        println("an IllegalArgumentException")
      }
    }
  }
}
and you can also take advantage of the scala conciseness like assertion and others, you can use the JunitWrapperSuite offered by the SCala test. 
// file 
//  junits_junit3suite.scala
package assertionAndUnitTesting.testing.junits


import junit.framework.TestCase
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
import assertionAndUnitTesting.assertions.Element.elem;
import org.scalatest.junit.JUnit3Suite


class ElementSuite extends JUnit3Suite {
  def testUniformElement() { 
    val ele = elem('x', 2, 3 )
    assert(ele.width == 2)
    expect (3) { 
      ele.height
    }
    intercept[IllegalArgumentException] { 
      elem('x', -2, 3)
    }
  }

}


informative failure reports. 

the way how it report the error is less informative, you might get an error message such as "3 did not equal to 2" in the failure reports. 

ScalaTest gives you the following construct to report meaningful infomraiton on the error/reports. 

  • expectResult - it is used to be expect
  • intercept

an example is shown as follow. 

// file
//   scalatest_informative_failure_report.,scala
// package

package assertionAndUnitTesting.testing.scalatests


import org.scalatest.Suite
import assertionAndUnitTesting.assertions._
import assertionAndUnitTesting.assertions.Element._

class InformativeSuite extends Suite {
  
	def testExpect() { 
	  val ele = elem('x', 2, 3)
	  // expect(2) { // expect has been deprecated
	  expectResult(2) { 
	    ele.width
	  }
	}
  
  def testInterceptException() {
    intercept[IllegalArgumentException] { 
      // throw 
      throw new IllegalArgumentException("Argument should not be null")
      elem('x', -2, 3 )
    }
  }
  
}

JUnit 

Junit is a very successful frameowrk of writing test for java classes, and as we already know that scala will be running on the Java runtime, and scala will compile into classes which can be understood by the junit runtime. 

// file 
//  junits_testcase.scala
package assertionAndUnitTesting.testing.junits


import junit.framework.TestCase
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
import assertionAndUnitTesting.assertions.Element.elem;


class ElementTestCase extends TestCase { 
  def testUnitformElement()  {
    val ele = elem('x', 2, 3)
    assertEquals(2, ele.width)
    assertEquals(3, ele.height)
    try { 
      elem('x', -2, 3)
      fail()
      
    }
    catch {
      case e : IllegalArgumentException =>  {
        println("an IllegalArgumentException")
      }
    }
  }
}


howeve, to run this test, you will need both to reference the ScalaTest and the Junit jars. 

Also, ScalaTest provides a Junit test wrapper - JUnit3Suite, with which you can leverage the scaltest's conciseness.

// file 
//  junits_junit3suite.scala
package assertionAndUnitTesting.testing.junits


import junit.framework.TestCase
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
import assertionAndUnitTesting.assertions.Element.elem;
import org.scalatest.junit.JUnit3Suite


class ElementSuite extends JUnit3Suite {
  def testUniformElement() { 
    val ele = elem('x', 2, 3 )
    assert(ele.width == 2)
    expect (3) { 
      ele.height
    }
    intercept[IllegalArgumentException] { 
      elem('x', -2, 3)
    }
  }

}

TestNG

Run with Testng annotationed tests. 
// file
//   testng_elementtests.scala
// package
//   assertionAndUnitTesting.testing.testng

package assertionAndUnitTesting.testing.testng

import org.testng.annotations.Test
import org.testng.Assert.assertEquals
import assertionAndUnitTesting.assertions.Element.elem

class ElementTests { 
  
  @Test 
  def verifyUniformElement() {
    val ele = elem('x', 2, 3)
    assertEquals(ele.width, 2)
    assertEquals(ele.height, 3)
    
  }
  
  @Test(
    expectedExceptions = Array(classOf[IllegalArgumentException])    
  )
  def elementShoudThrowIAE() {
    elem('x', -2, 3)
  }
}

To run the same test with the TestNG wrapper so to leverage the ScalaTest concise syntax, you can do .
// file
//   testng_scalawrapper.scala
// package
//   assertionAndUnitTesting.testing.testng

package assertionAndUnitTesting.testing.testng

import org.testng.annotations.Test
import org.testng.Assert.assertEquals
import org.scalatest.testng.TestNGSuite
import assertionAndUnitTesting.assertions.Element.elem

class ElementSuite extends TestNGSuite { 
  
  @Test 
  def verifyUniformElement() {
    val ele = elem('x', 2, 3)
    assertEquals(ele.width, 2)
    expect(3) {
      ele.height
    }
    intercept[IllegalArgumentException] {
      elem('x', -2, 3)
    }
  }
}


Tests as specification

Test as specification is the "Behavior-driven development(BDD)" the emphasis is to write human-readable specification on teh expected behavior of code, and accompanying test that verify the code has the specified behavior. Scalatest includes several traits - WordSpec, FlatSpec, and FeatureSpec - which facilitate this style of testing. 

// file 
//  specification_flatspecification.scala
// package:
//  assertionAndUnitTesting.testing.specifications
package assertionAndUnitTesting.testing.specifications

import assertionAndUnitTesting.assertions.Element.elem;
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers


class ElementSuite extends FlatSpec with ShouldMatchers {
  "A uniformElement" should  
     "having a width equal to the passed value" in {
    val ele = elem('x', 2, 3)
    ele.width should be (3)
  }
  
  it should "have a height equal to the passed value" in {
    val ele = elem('x', 2, 3)
    ele.height should be (3)
  }
  
  it should "throw an IAE if passed a negative width" in {
    evaluating { 
      elem('x', -2, 3)
      
    } should produce [IllegalArgumentException]
  }
}
This is with the ShouldMatcher, and you can re-write the code to have the MustMatcher. 
// file 
//  specification_mustmatcher.scala
// package:
//  assertionAndUnitTesting.testing.specifications
package assertionAndUnitTesting.testing.specifications

import assertionAndUnitTesting.assertions.Element.elem;
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.matchers.MustMatchers


class MustElementSuite extends FlatSpec with MustMatchers {
  "A uniformElement" should  
     "having a width equal to the passed value" in {
    val ele = elem('x', 2, 3)
    ele.width must be >= 0
    ele.width must be (3)
  }
  
  it should "have a height equal to the passed value" in {
    val ele = elem('x', 2, 3)
       // ele must have height 3 // -overloaded method value must with override ... cannot be applied to Element.
	    var arr = new Array(3)
	    arr must have length 3
	    var map = Map('a' -> 1, 'b' -> 2)
	    map must contain key 'c'  
    ele.height must be (3)
  }
  
  it should "throw an IAE if passed a negative width" in {
    evaluating { 
      elem('x', -2, 3)
      
    } must produce [IllegalArgumentException]
  }
}
There is a spec testing framework, which is similar to the MustMatcher and the ShouldMatcher , but it has different syntax, let' check this out. 
you may find more details on the specs jar is available here - specs framework
// file 
//  specification_specframework.scala
// package:
//  assertionAndUnitTesting.testing.specifications
package assertionAndUnitTesting.testing.specifications

import org.specs._
import assertionAndUnitTesting.assertions.Element.elem;
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers


class ElementSpecification extends Specification {
  "A uniformElement" should  {
     "having a width equal to the passed value" in {
    val ele = elem('x', 2, 3)
    ele.width must be_==(3)
  }
  
	"have a height equal to the passed value" in {
	    val ele = elem('x', 2, 3)
	    ele.height must be_==(3)
	  }
	  
	"throw an IAE if passed a negative width" in {
	    elem('x', -2, 3) must 
	    throwA[IllegalArgumentException]
	}
  }
}


Property-based check 

property based check let you specify the properties that the code under test must obey.  you can download a test framework called ScalaCheck , you can download here -  ScalaCheck


// file 
//  specification_propertybased.scala
// package:
//  assertionAndUnitTesting.testing.propertybased
package assertionAndUnitTesting.testing.propertybased

import assertionAndUnitTesting.assertions.Element.elem;
import org.scalatest.WordSpec
import org.scalatest.prop.Checkers
import org.scalacheck.Prop._


// ==> is the "implication operator", which means if the 
// left is true, then the right must hold true
class ElementSpecification extends WordSpec with Checkers {
  "elem result" must { 
    "have passed with " in { 
      check ((w : Int) => w > 0 ==> (elem('x', w, 3).width == w))
      
    }
    "have passed height " in { 
      check ((h : Int) => h > 0 ==> (elem('x', 2, h).height == h))
    }
  }
}

[PLACEHOLDER]:

This is a placholder we will come back to it later. 

References:

specs framework

ScalaCheck

你可能感兴趣的:(scala)