Reputation: 7466
The title almost says it all. My project is in Swift 2.3. Ocassionaly a custom struct model object is introduced. For various reasons, this must conform to Equatable.
Let's for example have a struct with 6 different value-type variables, all from Swift standard library.
struct Address: Equatable {
let firstName: String
let lastName: String
let street: String
let streetNumber: Int
let city: String
let countryCode: Int
}
func ==(lhs: Address, rhs: Address) -> Bool {
return (
lhs.firstName == rhs.firstName &&
lhs.lastName == rhs.lastName &&
lhs.street == rhs.street &&
lhs.streetNumber == rhs.streetNumber &&
lhs.city == rhs.city &&
lhs.countryCode == rhs.countryCode)
}
What is the correct algorithm to ensure I will test ALL the combinations of (in)equality in my unit test?
Permutations/combinations? Perhaps something more programatic?
Upvotes: 3
Views: 1010
Reputation: 36287
I'm a relative newbie on TDD but this what I have learned so far:
func performNotEqualTestWithAddresssProperties(firstNameF: String, firstNameS: String, LastNameF: String, LastNameS: String, streetF: String, streetS: String streetNumberF: Int, streetNumberS: Int, cityF: String, cityS: String, countryCodeF : Int, countryCodeS : Int){
let AddressFirst = Address(firstName: firstNameF, LastName:LastNameF, street: streetF, streetNumber: streetNumberF, city : cityF, countryCode: countryCodeF)
let AddressSecond = Address(firstName: firstNameS, LastName:LastNameS, street: streetS, streetNumber: streetNumberS, city : cityS, countryCode: countryCodeS)
XCTAssertNotEqual(AddressFirst, AddressSecond, "Addresses are not equal")
// 'F' means first, 'S' means second, I just avoided to not have long lines
// The helper function takes 2 sets of Addresses and Asserts if they ARE equal
}
func testWhenAddressDiffers_SHouldNotBeEqual() {
performNotEqualTestWithAddresssProperties(firstNameF: "Earl", firstNameS: "Earl", LastNameF: "Grey", LastNameS: "Grey", streetF: "south", streetS: "south", streetNumberF: 23, streetNumberS: 23, cityF: "silicon valley", cityS: "silicon valley", countryCodeF: 24, countryCodeS: 29)
//write another and switch firstName,another and switch LastName, ...
Here I only tested countryCode
. All other elements were the same.
to fully test your Address struct equality, each time you populate all properties of Address identical except for one. So basically you have to write this function 6 times.
You have 6 equalities to test. They are independent expressions and need to be treated independently. This is the best programmatic way I know of.
Upvotes: 0
Reputation: 25908
In this case, exhaustively testing all the combinations is just a flat-out poor strategy. It's unnecessary, and serves only to make unit testing more troublesome and makes you less likely to do it well.
With a very simple equality function like this, there's a good case to be made that you don't need to test it at all. If you must test it, testing one case for equality, and one case for inequality, is good enough. If you want to really go crazy, you can have one test for equality, and six tests for inequality in each of your members.
It's better to write out the test cases individually and manually, because you should be able to have faith in the correctness of a unit test simply by looking at it. If you can't, then you can end up in the rather absurd position of having to unit test your unit tests.
However, if you really absolutely insist on doing things the wrong way and exhaustively testing every possible combination, then you can do so like this:
func makeAddressPair(sames: [Bool]) -> (Address, Address) {
let firstName = ["John", sames[0] ? "John" : "Jane"]
let secondName = ["Smith", sames[1] ? "Smith" : "Williams"]
let street = ["Acacia Avenue", sames[2] ? "Acacia Avenue" : "Privet Close"]
let streetNumber = [10, sames[3] ? 10 : 21]
let city = ["Leeds", sames[4] ? "Leeds" : "Bolton"]
let country = [1, sames[5] ? 1 : 2]
return (Address(firstName: firstName[0], lastName: secondName[0], street: street[0],
streetNumber: streetNumber[0], city: city[0], countryCode: country[0]),
Address(firstName: firstName[1], lastName: secondName[1], street: street[1],
streetNumber: streetNumber[1], city: city[1], countryCode: country[1]))
}
class AddressCompareTests: XCTestCase {
func testAddressCompare() {
var comparesEqual = 0
var comparesInequal = 0
for a in [true, false] {
for b in [true, false] {
for c in [true, false] {
for d in [true, false] {
for e in [true, false] {
for f in [true, false] {
let (first, second) = makeAddressPair(sames: [a, b, c, d, e, f])
if first == second {
comparesEqual += 1
}
else {
comparesInequal += 1
}
}
}
}
}
}
}
XCTAssertEqual(comparesEqual, 1)
XCTAssertEqual(comparesInequal, 63)
}
}
You have 2 ^ 6 = 64 possible combinations, because you have six members, and between two structs, each pair of members can be either equal, or not equal. Here we have a helper function to generate a pair of structs based on a list of which members should be equal between them, and a nested loop to generate all possible combinations of pairs. When you compare all the pairs, exactly one should compare equal, and exactly 63 should compare unequal, so those are the test conditions we assert, since at least then we have some kind of poor cousin to quality control over the correctness of our test.
If instead you tested two or seven cases manually, it would be much simpler, clearer, easier to read and verify, and would take fewer lines of code, than doing it this way.
Upvotes: 2