I write code in a Test-Driven way and I often build my functions starting with tests for the easy edge cases.
For example, given a flat array of Items that have a category property, return an array of Groups, each built with all the items for one of the categories in the input.
One edge case I find helpful to test first is the behavior when the items are all from the same category. This gives me a chance to see how the Group should look like.
In this case there are (at least) two approaches one can take: two granular tests vs. a single one.
Granular
One could write two granular tests for these conditions:
- Given an array with items all from the same category, the output should be an array with a single group
- A group should contain all the items for its category and only those
In Swift, these tests might look something like this
func testGroupingArrayOfSameCategoryReturnsOneGroup() {
let items = [
Item(name: "a", category: .foo),
Item(name: "b", category: .foo),
Item(name: "c", category: .foo),
]
let groups = groupByCategory(items)
XCTAssertEqual(groups.count, 1)
}
func testGroupingBuildsGroupWithAllItemsForCategory() throws {
let items = [
Item(name: "a", category: .foo),
Item(name: "b", category: .foo),
Item(name: "c", category: .foo),
]
let groups = groupByCategory(items)
let group = try XCTUnwrap(groups.first)
XCTAssertEqual(group.category, .foo)
XCTAssertEqual(group.items.count, 3)
XCTAssertEqual(group.items[0].name, "a")
XCTAssertEqual(group.items[1].name, "b")
XCTAssertEqual(group.items[2].name, "c")
}
I like how the two behaviors of having a 1:1 match with groups and categories and how the groups are built are separated, but I see a lot of duplication between those tests.
Aggregated
A different approach would be to check both facets of the behavior in the same test.
func testGroupingArrayOfSameCategoryReturnsOneGroupWithAllItemsForCategory() {
let items = [
Item(name: "a", category: .foo),
Item(name: "b", category: .foo),
Item(name: "c", category: .foo),
]
let groups = groupByCategory(items)
XCTAssertEqual(groups.count, 1)
let group = try XCTUnwrap(groups.first)
XCTAssertEqual(group.category, .foo)
XCTAssertEqual(group.items.count, 3)
XCTAssertEqual(group.items[0].name, "a")
XCTAssertEqual(group.items[1].name, "b")
XCTAssertEqual(group.items[2].name, "c")
}
Which do you think is clearer and why?
testGroupingArrayOfSameCategoryReturnsOneGroupWithAllItemsForCategoryfor the most-verbose-function-name-of-2022 award. \$\endgroup\$