我有3种与特定类相关的方法,其定义如下:
class MyClass: NSObject {
func myMethod() {
methodA()
methodB()
methodC()
}
func methodA() {}
func methodB() {}
func methodC() {}
}
我需要测试myMethod
是否按实现的顺序调用了所有3种方法:methodA
然后methodB
然后methodC
要使用XCode单元测试进行测试,无论这些方法的实现如何,我都在测试用例中创建了一个子类,如下所示:
class ChildClass: MyClass {
var method1CallingDate: Date?
var method2CallingDate: Date?
var method3CallingDate: Date?
override func methodA() {
super.methodA()
method1CallingDate = Date()
}
override func methodB() {
super.methodB()
method2CallingDate = Date()
}
override func methodC() {
super.methodC()
method3CallingDate = Date()
}
}
现在在测试方法中,我首先调用这三个方法,然后断言所有三个日期都不都是零,然后像这样比较它们:
XCTAssertLessThan(method1CallingDate, method2CallingDate)
XCTAssertLessThan(method2CallingDate, method3CallingDate)
我遇到的问题是测试有时成功有时失败,我猜是由于Date
对象(随机)在两个方法调用之间是相同的。
有没有更好的方法来测试调用多个方法的顺序?
p.s。。这很容易在Android SDK org.mockito.Mockito.inOrder
答案 0 :(得分:2)
首先,制作一个模拟对象来记录订单。没有日期,没有字符串。只是一个枚举。
class MockMyClass: MyClass {
enum invocation {
case methodA
case methodB
case methodC
}
private var invocations: [invocation] = []
override func methodA() {
invocations.append(.methodA)
}
override func methodB() {
invocations.append(.methodB)
}
override func methodC() {
invocations.append(.methodC)
}
func verify(expectedInvocations: [invocation], file: StaticString = #file, line: UInt = #line) {
if invocations != expectedInvocations {
XCTFail("Expected \(expectedInvocations), but got \(invocations)", file: file, line: line)
}
}
}
然后在测试中:
mock.verify(expectedInvocations: [.methodA, .methodB, .methodC])
没有异步等待。通话简单。清除失败消息。
答案 1 :(得分:1)
您可以使用String
做这样的事情来跟踪订单:
class ChildClass: MyClass {
var order = ""
override func methodA() {
super.methodA()
order = String((order + "A").suffix(3))
}
override func methodB() {
super.methodB()
order = String((order + "B").suffix(3))
}
override func methodC() {
super.methodC()
order = String((order + "C").suffix(3))
}
}
然后,只需检查order
是"ABC"
。
或者,如果在B
和A
之间多次调用C
是有效的:
class ChildClass: MyClass {
var order = ""
override func methodA() {
super.methodA()
order = order.replacingOccurrences(of: "A", with: "") + "A"
}
override func methodB() {
super.methodB()
order = order.replacingOccurrences(of: "B", with: "") + "B"
}
override func methodC() {
super.methodC()
order = order.replacingOccurrences(of: "C", with: "") + "C"
}
}
示例:
let c = ChildClass()
c.methodA()
c.methodB()
c.methodB()
c.methodC()
print(c.order)
ABC
答案 2 :(得分:1)
我已经喜欢使用XCTestExpectation
做这种事情。这是一个选择。
class MyTestableClass: MyClass {
var methodAHandler: (() -> Void)?
// ...
override func methodA() {
methodAHandler?()
super.methodA()
}
然后在您的测试用例中
let expA = XCTestExpectation(description: "Method A Called")
let expB = ...
let expo = ...
objectUnderTest.methodAHandler = { expA.fulfill() }
/// ...
objectUnderTest.myMethod()
// ensure you use the enforceOrder param, which is optional
wait(for: [expA, expB, expC], timeout: 1.0, enforceOrder: true)
XCTestExpectation
可以用于异步测试,因此等待有点有趣。但是,它确实可以满足您的需求,即使myMethod
的内部由于某种原因最终变得异步时,它也可以继续工作。
虽然我本人还没有使用过它,但您可能还想查看Cuckoo。这是Swift的模拟框架。
答案 3 :(得分:1)
您在这里提出的问题不是正确的。从单元测试的角度来看,您不应该知道/关心被测试的方法是否调用其他方法,甚至不存在其他方法。
单元测试应验证测试方法的一些可观察到的结果。在单元测试的上下文中,测试方法内部发生的任何事情都是无关紧要的。
这是因为单元测试应该验证单元的行为是否符合预期,即它们应该根据规范而不是实现进行验证。
让我们考虑一个简单的示例,对isPrime(n)
函数进行单元测试。除非进行性能测试,否则您只关心函数是否为几个数字返回适当的结果。您不必担心函数是否检查所有可能的除数,或者是否使用所有已知素数的数据库,或者是否将素数检查委托给某些第三方库/服务。
情况与您没有太大不同。必须通过被测单元的外部接口来验证以某种顺序调用这三种方法的事实。例如,如果这三种方法进行API调用,则模拟API客户端,并期望它被请求三次,并带有预期的URL /有效负载。如果调用这三种方法没有引起任何明显的变化,那么从一开始就没有太多可测试的内容,因此,以某种顺序调用这三种方法的事实也变得无关紧要。
单元测试是关于验证该单元执行结果的,仅此而已。现在,在命令式编程语言中,input-> output函数是少数,但这并不意味着我们不能间接测试该函数的行为是否符合预期。您可以使用模拟,或在函数执行后验证对象的某些属性。再次,如果没有办法从外部检查方法的顺序,那么您就没有要验证的规范。