Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Exercises

Below are three exercises designed to help you practice writing tests using ScalaTest. These exercises cover different testing styles and use cases, including unit testing, property-based testing, and integration testing.

Exercise 1: Unit Testing with FunSuite

Objective: Write unit tests for a StringUtility object that provides a method to reverse a string and another method to check if a string is a palindrome.

  1. Implement the StringUtility object:
object StringUtility {
  def reverse(s: String): String = s.reverse
  def isPalindrome(s: String): Boolean = s == s.reverse
}
  1. Write tests using FunSuite: Create a test class StringUtilityTest using FunSuite to test both methods. Test normal strings, empty strings, and palindromes.

Exercise 2: Testing a Shopping Cart

Objective: Write tests for a ShoppingCart class that supports adding items, removing items, and calculating the total price. The cart should handle quantities for items and provide an option to apply a discount code.

  1. Implement the ShoppingCart and Item classes:
case class Item(id: String, name: String, price: Double, quantity: Int)

class ShoppingCart {
  private var items: Map[String, Item] = Map()

  def addItem(item: Item): Unit = {
    items = items.get(item.id) match {
      case Some(existingItem) => items.updated(item.id, existingItem.copy(quantity = existingItem.quantity + item.quantity))
      case None => items + (item.id -> item)
    }
  }

  def removeItem(itemId: String): Unit = {
    items -= itemId
  }

  def applyDiscount(code: String): Unit = {
    val discountRate = code match {
      case "DISCOUNT10" => 0.1
      case _ => 0.0
    }
    items = items.view.mapValues(item => item.copy(price = item.price * (1 - discountRate))).toMap
  }

  def total: Double = {
    items.values.map(item => item.price * item.quantity).sum
  }
}
  1. Write tests to verify:
  • Adding and removing items updates the cart correctly.
  • The total price is calculated correctly, with and without a discount code.

Exercise 3: Testing a Password Validator

Objective: Write tests for a PasswordValidator object that checks if a password meets certain criteria: it must be at least 8 characters long, contain both uppercase and lowercase letters, and at least one number.

  1. Implement the PasswordValidator:
object PasswordValidator {
  def isValid(password: String): Boolean = {
    password.length >= 8 &&
    password.exists(_.isUpper) &&
    password.exists(_.isLower) &&
    password.exists(_.isDigit)
  }
}
  1. Write tests to verify:
  • A password meeting all criteria is valid.
  • Passwords missing each specific criterion are invalid.
  • Edge cases, such as an empty string or a password with only spaces.

Exercise 4: Property-Based Testing for a String Concatenation Utility

Objective: Use ScalaCheck to write property-based tests for a StringConcatenationUtility that concatenates two strings with a space between them.

  1. Implement the StringConcatenationUtility:
object StringConcatenationUtility {
  def concatenate(str1: String, str2: String): String = s"$str1 $str2"
}
  1. Write property-based tests to verify:
  • Concatenating two non-empty strings results in a string that contains both, separated by a space.
  • Concatenating an empty string with any string returns the other string with a leading or trailing space.
  • The length of the result is the sum of the lengths of the two strings plus one (for the space).

Exercise 5: Testing a Fibonacci Number Generator

Objective: Write tests for a FibonacciGenerator that generates the nth Fibonacci number. Consider both iterative and recursive implementations.

  1. Implement the FibonacciGenerator:
object FibonacciGenerator {
  def fibonacci(n: Int): Int = n match {
    case 0 | 1 => n
    case _ => fibonacci(n - 1) + fibonacci(n - 2)
  }
}
  1. Write tests to verify:
  • Correct Fibonacci numbers are generated for known values (e.g., 0, 1, 1, 2, 3, 5, 8, 13, ...).
  • Negative numbers return 0 or an appropriate error/exception.
  • Performance or stack overflow issues for larger n (e.g., n = 50), if applicable.

Exercise 6: Integration Testing for a File Processing Utility

Objective: Write integration tests for a FileProcessingUtility that reads a file, processes its content, and writes the result to another file. Assume the processing involves converting all text to uppercase.

  1. Implement the FileProcessingUtility (simplified for exercise):
import scala.io.Source
import java.nio.file.{Files, Paths}

object FileProcessingUtility {
  def processFile(inputPath: String, outputPath: String): Unit = {
    val source = Source.fromFile(inputPath)
    val content = source.getLines().mkString("\n").toUpperCase
    source.close()
    Files.write(Paths.get(outputPath), content.getBytes)
  }
}
  1. Write tests to verify:
  • Correct processing of an input file with known content.
  • Handling of non-existent input files.
  • The output file is created in the specified location with the expected processed content.