第16章——单元测试

代码将总是按照被编写的行为运行—单元测试将确保它做的确实符合编写者的本意。 在开发应用程序的过程中,单元测试还有助于确保代码行为持续符合预期。

16.1 使用 JUnit

UnitTesting/UsingJUnit.scala

import java.util

import org.junit.Assert._
import org.junit.Test

class UsingJUnit {
  @Test
  def listAdd(): Unit = {
    val list = new util.ArrayList[String]
    list.add("Milk")
    list.add("Sugar")
    assertEquals(2, list.size)
  }
}

执行命令

scalac -d classes -classpath $JUNIT_JAR:$HAMCREST_JAR UsingJUnit.scala
java -classpath $SCALALIBRARY:$JUNIT_JAR:$HAMCREST_JAR:classes \
  org.junit.runner.JUnitCore UsingJUnit

运行结果

JUnit version 4.12
.
Time: 0.003

OK (1 test)

16.2 使用 ScalaTest

UnitTesting/UsingScalaTest.scala

import java.util
import org.scalatest._

class UsingScalaTest extends FlatSpec with Matchers {
  trait EmptyArrayList {
    val list = new util.ArrayList[String]
  }

  "a list" should "be empty on create" in new EmptyArrayList {
    list.size should be(0)
  }

  "a list" should "increase in size upon add" in new EmptyArrayList {
    list.add("Milk")
    list.add("Sugar")

    list.size should be(2)
  }
}

执行命令

scalac -d classes -classpath $SCALA_TEST_JAR UsingScalaTest.scala
scala -classpath $SCALA_TEST_JAR:classes org.scalatest.run UsingScalaTest

运行结果

Run starting. Expected test count is: 2
UsingScalaTest:
a list
- should be empty on create
a list
- should increase in size upon add
Run completed in 181 milliseconds.
Total number of tests run: 2
Suites: completed 1, aborted 0
Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
All tests passed.

16.3 使用 Mockito

UnitTesting/WordScorerTest.scala (withoutmock)

import org.scalatest.{ FlatSpec, Matchers }

class WordScorerTest extends FlatSpec with Matchers {

  def withWordScorer(test: WordScorer => Unit): Unit = {
    val wordScorer = new WordScorer()

    test(wordScorer)
  }

  "score" should "return 0 for an empty word" in {
    withWordScorer { wordScorer =>
      wordScorer.score("") should be(0)
    }
  }

  "score" should "return 2 for word with two vowels" in {
    withWordScorer { _.score("ai") should be(2) }
  }

  "score" should "return 8 for word with four consonants" in {
    withWordScorer { _.score("myth") should be(8) }
  }

  "score" should "return 7 for word with a vowel and three consonants" in {
    withWordScorer { _.score("that") should be(7) }
  }
}

UnitTesting/WordScorer.scala (withoutmock)

class WordScorer() {
  private val VOWELS = List('a', 'e', 'i', 'o', 'u')

  def score(word: String): Int = {
    (0 /: word) { (total, letter) =>
      total + (if (VOWELS.contains(letter)) 1 else 2)
    }
  }
}

执行命令

scalac -d classes -classpath $SCALA_TEST_JAR \
  WordScorer.scala WordScorerTest.scala
scala -classpath $SCALA_TEST_JAR:classes org.scalatest.run WordScorerTest

运行结果

Run starting. Expected test count is: 4
WordScorerTest:
score
- should return 0 for an empty word
score
- should return 2 for word with two vowels
score
- should return 8 for word with four consonants
score
- should return 7 for word that with a vowel and three consonants
Run completed in 181 milliseconds.
Total number of tests run: 4
Suites: completed 1, aborted 0
Tests: succeeded 4, failed 0, canceled 0, ignored 0, pending 0
All tests passed.

UnitTesting/SpellChecker.scala (withmock)

trait SpellChecker {
  def isCorrect(word: String): Boolean
}

UnitTesting/WordScorerTest.scala (withmock)

import org.scalatest.{ FlatSpec, Matchers }
import org.mockito.Mockito._
import org.mockito.ArgumentMatchers.anyString

class WordScorerTest extends FlatSpec with Matchers {

  def withWordScorer(test: WordScorer => Unit): Boolean = {
    val spellChecker = mock(classOf[SpellChecker])
    when(spellChecker.isCorrect(anyString)).thenReturn(true)
    val wordScorer = new WordScorer(spellChecker)

    test(wordScorer)

    verify(spellChecker, times(1)).isCorrect(anyString())
  }

  //No change to the tests, same as in the previous version
  "score" should "return 0 for an empty word" in {
    withWordScorer { wordScorer =>
      wordScorer.score("") should be(0)
    }
  }

  "score" should "return 2 for word with two vowels" in {
    withWordScorer { _.score("ai") should be(2) }
  }

  "score" should "return 8 for word with four consonants" in {
    withWordScorer { _.score("myth") should be(8) }
  }

  "score" should "return 7 for word that with a vowel and three consonants" in {
    withWordScorer { _.score("that") should be(7) }
  }

}

UnitTesting/WordScorer.scala (withmock)

class WordScorer(val spellChecker: SpellChecker) {
  private val VOWELS = List('a', 'e', 'i', 'o', 'u')

  def score(word: String): Int = {
    spellChecker.isCorrect(word)
    (0 /: word) { (total, letter) =>
      total + (if (VOWELS.contains(letter)) 1 else 2)
    }
  }
}

执行命令

scalac -d classes -classpath $SCALA_TEST_JAR:$MOCKITO_JAR \
  WordScorer.scala SpellChecker.scala WordScorerTest.scala
scala -classpath $SCALA_TEST_JAR::$MOCKITO_JAR:classes \
  org.scalatest.run WordScorerTest

运行结果

Run starting. Expected test count is: 4
WordScorerTest:
score
- should return 0 for an empty word
score
- should return 2 for word with two vowels
score
- should return 8 for word with four consonants
score
- should return 7 for word that with a vowel and three consonants
Run completed in 316 milliseconds.
Total number of tests run: 4
Suites: completed 1, aborted 0
Tests: succeeded 4, failed 0, canceled 0, ignored 0, pending 0
All tests passed.

UnitTesting/WordScorerTest.scala (withmock2)

"score" should "return 0 for word with incorrect spelling" in {
  val spellChecker = mock(classOf[SpellChecker])
  when(spellChecker.isCorrect(anyString)).thenReturn(false)
  val wordScorer = new WordScorer(spellChecker)

  wordScorer.score("aoe") should be(0)
  verify(spellChecker, times(1)).isCorrect(anyString())
}

UnitTesting/WordScorer.scala (withmock2)

def score(word: String): Int = {
  if (spellChecker.isCorrect(word))
    (0 /: word) { (total, letter) =>
      total + (if (VOWELS.contains(letter)) 1 else 2)
    } else
    0
}

运行结果

Run starting. Expected test count is: 5
WordScorerTest:
score
- should return 0 for an empty word
score
- should return 2 for word with two vowels
score
- should return 8 for word with four consonants
score
- should return 7 for word that with a vowel and three consonants
score
- should return 0 for word with incorrect spelling
Run completed in 208 milliseconds.
Total number of tests run: 5
Suites: completed 1, aborted 0
Tests: succeeded 5, failed 0, canceled 0, ignored 0, pending 0
All tests passed.