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.
- Implement the
StringUtilityobject:
object StringUtility {
def reverse(s: String): String = s.reverse
def isPalindrome(s: String): Boolean = s == s.reverse
}
- Write tests using
FunSuite: Create a test classStringUtilityTestusingFunSuiteto 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.
- Implement the
ShoppingCartandItemclasses:
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
}
}
- 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.
- Implement the
PasswordValidator:
object PasswordValidator {
def isValid(password: String): Boolean = {
password.length >= 8 &&
password.exists(_.isUpper) &&
password.exists(_.isLower) &&
password.exists(_.isDigit)
}
}
- 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.
- Implement the
StringConcatenationUtility:
object StringConcatenationUtility {
def concatenate(str1: String, str2: String): String = s"$str1 $str2"
}
- 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.
- Implement the
FibonacciGenerator:
object FibonacciGenerator {
def fibonacci(n: Int): Int = n match {
case 0 | 1 => n
case _ => fibonacci(n - 1) + fibonacci(n - 2)
}
}
- 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.
- 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)
}
}
- 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.