别测试代码,而应该验证程序的行为

测试应该只在程序的行为和预期的不一样时,才不通过。测试应该测试程序的代码做了什么,而不是测试程序如何实现。

  • 验证应用程序做了什么的,叫做行为测试
  • 即使应用程序的行为不发生变化,只要应用程序的代码发生了变化,测试就不通过的,叫做脆性测试

假设我们有一个香蕉数据库,叫做 GorillaDBGorillaDB 是一个以键-值对来储存香蕉的数据库。我们可以用这样的方式储存香蕉:

let database = GorillaDB()
let banana = Banana()
database.save(banana: banana, key: "my-banana")

之后可以从数据库里取回香蕉:

let banana = database.load(key: "my-banana")

脆性测试

我们如何测试这个存取的行为呢?一种方式是每当我们储存一根香蕉后就检查数据库的大小:

// GorillaDBTests.swift

func testSave_savesTheBananaToTheDatabase() {
// Arrange: Create a database and get its original size.
let database = GorillaDB()
let originalSize = database.size

// Act: Save a banana to the database.
let banana = Banana()
database.save(banana: banana, key: "test-banana")

// Assert: The size of the database should have increased by one.
XCTAssertEqual(database.size, originalSize + 1)
}

然而,设想一下 GorillaDB 的源代码发生了变化。为了从数据库里更快地取出香蕉,数据库预留了一部份缓存空间用于存放经常使用的香蕉。GorillaDB.size 就会随着缓存的增加而增加,这样我们的测试就不能通过了:

行为测试

编写行为测试的关键,就是准确的定位你想让你的程序代码做什么。

在我们的 testSave_savesTheBananaToTheDatabase 的测试中:当我们在数据库中储存一根香蕉时,我们所希望程序完成的是一个怎样的行为呢?应该是保存香蕉,即之后可以取回香蕉。因此,我们不该测试数据库大小的增加,而应该测试我们能不能从数据库里取回香蕉。

// GorillaDBTests.swift

func testSave_savesTheBananaToTheDatabase() {
// Arrange: Create a database and get its original size.
let database = GorillaDB()
-  let originalSize = database.size

// Act: Save a banana to the database.
let banana = Banana()
database.save(banana: banana, key: "test-banana")

-  // Assert: The size of the database should have increased by one.
-  XCTAssertEqual(database.size, originalSize + 1)
+  // Assert: The bananas saved to and loaded from the database should be the same.
+  XCTAssertEqual(database.load(key: "test-banana"), banana)
}

编写行为测试的关键,就在于思考这些问题:

  • 这段程序代码是用来做什么的?
  • 我的测试只验证了程序的行为吗?它可能因为代码运行的其他原因而不通过吗?

results matching ""

    No results matching ""