June 24, 2010

Test results QuickCheck and RSpec

There are lame code-snippet in posts bellow:



  1. class Rational from Programming in Scala




  2. org.scalatest.Spec for Rational




  3. org.scalacheck a.k.a QuickCheck


They helped me to decide on my personal preference in testing style.

Conditionally on task at hand, but I'm inclined towards RSpec and QuickCheck tools.



While tossing code around my tests caught two not trivial BUGs.



BUG 1: Rational should normalize not only the quotient but also sign of nominator and denominator.


BUG 2: Scala library description of Ordering isn't accurate.

trait   Ordering[T] extends Comparator[T] with PartialOrdering[T]   
Theorem:
transitive: 
if 
   compare(x, y) == z 
   and compare(y, w) == v 
   and math.signum(z) >= 0 
   and math.signum(v) >= 0 
then 
   compare(x, w) == u 
   and math.signum(z + v) == math.signum(u), 

for any x, y, and w of type T and z, v, and u of type Int.



Both were detected by ScalaCheck tests.

So, it is 2:0 for QuickCheck against RSpec on foreign (Scala) field.

org.scalacheck a.k.a QuickCheck

Code from Rational spec use something which belong to different testing style:

package funobject
 it("should have a total order") { 
      import RationalOrderingCheck._

      check(propertyReflexive)  should be ('passed)
      check(propertySymmetry)   should be ('passed)
      check(propertyTransitive) should be ('passed)
    }

It is QuickCheck fixture.

"ScalaCheck is a powerful tool for automatic unit testing",
normal unit testing considered `manual and unreliable.

package funobject

import org.scalatest.FunSuite
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.tools._

import org.scalacheck._
import org.scalacheck.Prop._
import org.scalacheck.Arbitrary.arbitrary


object ArbitraryRational { 

  import funobject._

  implicit def arbRational: Arbitrary[Rational]  = Arbitrary { 

    val genRational = for { 
      n <- Gen.choose(-5, 5)
      d <- Gen.choose(-6, 6) suchThat (_ != 0)
    } yield Rational(n, d)
    
    genRational
  }

}

object RationalOrderingCheck {  

  import ArbitraryRational._

  /** reflexive: compare(x, x) == 0, for any x of type T.
   */ 
  val propertyReflexive = forAll { 
    r: Rational =>  (r.compare(r) == 0)
  }  

  /** symmetry: compare(x, y) == z and compare(y, x) == w
   * then math.signum(z) == -math.signum(w),
   * for any x and y of type T and z and w of type Int.
   */ 
  val propertySymmetry = forAll { 
    (x: Rational, y:Rational) =>  { 
      val z = x.compare(y)
      val w = y.compare(x)
      math.signum(z) == -math.signum(w)
    }  
  }

  /** transitive: if compare(x, y) == z and compare(y, w) == v
   * and math.signum(z) >= 0 and math.signum(v) >= 0
   * then compare(x, w) == u and math.signum(z + v) == math.signum(u),
   * for any x, y, and w of type T and z, v, and u of type Int.
   */ 
  val propertyTransitive = forAll { 
    (x: Rational, y:Rational, w: Rational) =>  { 
      val z = x.compare(y)
      val v = y.compare(x)
      val u = x.compare(w)

      if (math.signum(z) > 0 &&
          math.signum(v) > 0) 
        math.signum(u) > 0
      else if (math.signum(z) < 0 &&
          math.signum(v) < 0) 
        math.signum(u) < 0
      else true
    }  
  }

}  




org.scalatest.Spec for Rational

A rational number is a number that can be expressed as a ratio d/n ....

package funobject

import org.scalacheck.Test.check
import org.scalacheck.ConsoleReporter.testReport

import org.scalatest.Spec
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.tools._
import funobject.Rational._
import funobject._

class RationalSpec extends Spec with ShouldMatchers { 
  // shorthand for Rational object
  val R = Rational 

  describe("A Rational Number") { 
    
    it("should throw IllegalArgumentException " +
       "if zero denuminator pased to ctor") { 
      evaluating { val e = R(1, 0) } should produce [IllegalArgumentException]      
    }

    it("should implement +, -, *, / and unary -") { 
      R(1,2) + R(2, 3) should have { 
        'nomer (7)
        'denom (6)
      }

      R(3,2) - R(1, 3) should have { 
        'nomer (7)
        'denom (6)
      }

      R(1, 3) * R(2, 5) should have { 
        'nomer (2)
        'denom (15)
      }

      R(1, 3) / R(2, 5) should have { 
        'nomer (5)
        'denom (6)
      }

      -R(1, 2) should have { 
        'nomer (-1)
        'denom (2)
      }
    }

    it("should have normal form") { 
      R(22, 42) should be === R(11, 21)
    }

    it("should support mixed ariphmetic" +
       " with implicit cast from integer") { 
      R(1,2) / 7 + (1 - R(2,3)) should be === R(-11, 42)
    }
    
    it("should have a total order") { 
      import RationalOrderingCheck._

      check(propertyReflexive)  should be ('passed)
      check(propertySymmetry)   should be ('passed)
      check(propertyTransitive) should be ('passed)
    }
  }
  
}


class Rational from Programming in Scala

A specification for class Rational from Programming in Scala

package funobject

/** Rational number is a number that can be expressed as ratio of n/d,
 * where n and d are integers, except that d can not be zero.
 */

class Rational(n: Int, d: Int) extends Ordered[Rational] {
  require(d != 0)

  private val g = gcd(n.abs, d.abs)
  val nomer = (if (d < 0) -n else n) / g
  val denom = d.abs / g

  def this(n: Int) = this(n, 1)


  def unary_- = new Rational(-nomer, denom)

  def + (that: Rational):Rational =
    new Rational (nomer * that.denom + that.nomer * denom,
                  denom * that.denom)
  def - (that: Rational):Rational =  -this + that

  def * (that: Rational):Rational =
    new Rational (nomer * that.nomer,
                  denom * that.denom)
  def / (that: Rational):Rational =
    new Rational (nomer * that.denom,
                  denom * that.nomer)

  override def equals (other: Any): Boolean = other match { 
    case that: Rational => this.nomer == that.nomer &&
                           this.denom == that.denom
    case _ => false

  }

  override def compare(that: Rational) = 
    this.nomer * that.denom - that.nomer * this.denom

  override def compareTo(that: Rational) = this.compare(that)

  override def <= (that: Rational): Boolean = compare(that) <= 0
  override def >= (that: Rational): Boolean = compare(that) >= 0
  override def <  (that: Rational): Boolean = compare(that) <  0
  override def >  (that: Rational): Boolean = compare(that) > 0


  override def toString = nomer + "/" + denom

  private def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a % b)
}

object Rational {
  implicit def implicitRationalFromInt(i: Int): Rational = new Rational(i)
  def apply(n: Int, d: Int) = new Rational(n, d)
  def apply(n: Int) = new Rational(n)
}