使用虚假和随机数据进行测试
使用随机假数据进行测试
我们在编写单元测试时都需要测试数据。
传统方法 - 我们自己生成模型。 例如,以下代码包含测试数据产品。
it must "return total cost of a product when the delivery cost is ignored" in {
val product = Product(id=1, name="test-product", unitPrice=10, deliveryCost=1)
dao.saveProduct(product)
val result = dao.calculateCost(product.id, includeDeliveryCost = false)
result must be(product.unitPrice)
}
这种方法有什么问题?
- 耗时(如果模型很大)。
- 测试主体可能很长(如果模型很大),并且它们主要由测试数据占据。
- 在我们自己生成测试数据时,我们可能会错过一些极端情况(本故事稍后会介绍这一点)。
另一种方法是使用生成器 [1] 生成虚假和随机测试数据,如下所示。
it must "return total cost of a product when the delivery cost is ignored" in {
// generate a random product
val product = genProduct()
dao.saveProduct(product)
val resultFromDB = dao.calculateCost(product.id, includeDeliveryCost = false)
resultFromDB must be(product.unitPrice)
}
我为什么要那么做?
- 每次运行测试时都会有一个新的输入。
- 易于生成测试数据。
- 测试主体是干净的(测试数据的代码更少)。
- 生成器驱动的测试有时会捕获我们不知道的错误。 例如,我能够捕捉到数据库拒绝存储 Unicode 字符的错误。
使您的预期结果明确而详细
如前所述,生成器驱动测试 [1] 帮助我们生成测试数据。
但是当预期结果很复杂时,它有时会增加单元测试的复杂性。
比如之前的单元测试,DB返回的结果,预期的结果简单易懂。
resultFromDB 必须是(product.unitPrice)
但是,在某些情况下,预期结果很复杂,例如学生的进度报告。
case class ProgressReport(
minScore: Int,
maxScore: Int
)
val myTestData = ???
// 这里我们试图从我们的
// 伪造的测试数据。
// 它添加:
// 1. 不必要的复杂性。
// 2. 难以推理
// 3. 不明确
val expectedProgressReport: ProgressReport = myFakeData.map( ... )
reportFromDB must be(expectedProgressReport)
因此,无论生成器如何,都最好使您的预期结果明确而详细。
让我们通过一个例子来理解这一点。
想象一下,我们想添加一个功能来计算学生的进度报告。
def calculateProgress(
studentId: Long
): ProgressReport = ???
首先,我们使用生成器 [1] 生成模型数据,并仅覆盖那些可能影响我们最终结果的属性。
val student = genStudent()
val course = genCourse()
// 因为它是关于计算分数的,所以我们只修改了 // score 属性。 这样,很容易推理。
val studentScore = genStudentScore(course.id, student.id).copy(score = 10)
然后,我们明确地写出预期的结果。
val expectedResult = ProgressReport(minScore = 10, maxScore = 10)
因此,我们的单元测试如下所示。
it should "calculate progress report of a student" in {
val student = genStudent()
val courseA = genCourse()
val courseB = genCourse()
// we are only modifying score attribute
val studentScoreForCourseA = genStudentScore(courseA.id, student.id).copy(score = 80)
val studentScoreForCourseB = genStudentScore(courseB.id, student.id).copy(score = 90)
// Writing expected results explicitly. This way, it's easy to
// read and reason about.
val expectedResult = ProgressReport(minScore = 80, maxScore = 90)
// Add test data to our system here
// i.e student record, his courses, and score in each course.
// Retrieve progress from DB.
val progressFromDB = dao.calculateProgress(student)
progressFromDB must be(expectedResult)
}
谢谢阅读。
如果您有任何问题,请随时提问。 我很乐意回答。
关注七爪网,获取更多APP/小程序/网站源码资源!