用 Swift 模拟
Mocking with Swift
考虑以下 class 命名为 SomeClass 写在 Swift:
@objc class SomeClass: NSObject
{
var shouldCallBar = false
func foo()
{
if (shouldCallBar == true)
{
bar()
}
}
func bar()
{
}
}
为了测试上面的 class foo() 方法(以及大部分写在 Objective-C 中的类似场景)我使用的 OCMock 如下:
- (void) testFooBarShouldBeCalledWhenShouldCallBarIsTrue
{
SomeClass * someClass = [SomeClass new];
// Create mocks.
id mockSomeClass = OCMPartialMock(someClass);
// Expect.
[[mockSomeClass expect] bar];
// Stub.
someClass.shouldCallBar = YES;
// Run code under test.
[someClass foo];
// Verify.
[mockSomeClass verify];
// Stop mocking.
[mockSomeClass stopMocking];
}
但是上面的测试失败了 Swift 代码为 OCMock won't works well with Swift.
所以我正在考虑完全在 Swift:
class SomeClassTests: XCTestCase
{
class MockSomeClass: SomeClass
{
var isBarCalled = false
override func bar()
{
isBarCalled = true
}
}
func testBarShouldBeCalledWhenTrue()
{
let someClass = MockSomeClass()
someClass.shouldCallBar = true
someClass.foo()
XCTAssertTrue(someClass.isBarCalled == true)
}
}
请注意,我正在对原始 class 进行子class 测试并覆盖 bar()。我完全没有触及 foo() 实现。
但缺点是我正在使用 MockSomeClass 实例来测试 SomeClass 的 foo() ].这是我不喜欢的 .
上面的问题有没有更好的解决办法?
备注:
- 我不是在这里谈论依赖注入。依赖注入是完全不同的方法。
- 我在 UIViewController.
中测试 UI 代码时遇到这类问题
- 我想到了基于协议的编程,但无法提出解决上述问题的方法。
因此,您想测试一个方法 (foo
) 调用或不调用另一个方法 (bar
)。 foo
方法是被测的,bar
方法是广义上的依赖组件。
如果 bar
的调用有持久的副作用,您可以测试副作用 is/isn 不存在,也许通过查询 属性 或类似的。在那种情况下,您不需要模拟或类似的东西。
如果没有副作用,那么您必须替换依赖项。为此,您需要一个 seam,您可以在其中放置可以告诉测试是否已调用该方法的代码。为此,我只能看到 Jon 已经在 中讨论过的两个选项。
您可以将这两种方法放在单独的 class 中,在这种情况下 class 边界就是接缝。使用一个协议,或者只是一个非正式的约定,你可以完全替换实现 bar
的 class。依赖注入在这里派上用场。
如果这两个方法必须保持相同 class 那么您必须使用子class 边界作为接缝,即您可以使用可以覆盖方法并实现 test-specific sublass。当您可以使用模拟框架时,这是最简单的。如果那不是一个选项,您必须自己编写代码,就像您在问题中描述的那样。
考虑以下 class 命名为 SomeClass 写在 Swift:
@objc class SomeClass: NSObject
{
var shouldCallBar = false
func foo()
{
if (shouldCallBar == true)
{
bar()
}
}
func bar()
{
}
}
为了测试上面的 class foo() 方法(以及大部分写在 Objective-C 中的类似场景)我使用的 OCMock 如下:
- (void) testFooBarShouldBeCalledWhenShouldCallBarIsTrue
{
SomeClass * someClass = [SomeClass new];
// Create mocks.
id mockSomeClass = OCMPartialMock(someClass);
// Expect.
[[mockSomeClass expect] bar];
// Stub.
someClass.shouldCallBar = YES;
// Run code under test.
[someClass foo];
// Verify.
[mockSomeClass verify];
// Stop mocking.
[mockSomeClass stopMocking];
}
但是上面的测试失败了 Swift 代码为 OCMock won't works well with Swift.
所以我正在考虑完全在 Swift:
class SomeClassTests: XCTestCase
{
class MockSomeClass: SomeClass
{
var isBarCalled = false
override func bar()
{
isBarCalled = true
}
}
func testBarShouldBeCalledWhenTrue()
{
let someClass = MockSomeClass()
someClass.shouldCallBar = true
someClass.foo()
XCTAssertTrue(someClass.isBarCalled == true)
}
}
请注意,我正在对原始 class 进行子class 测试并覆盖 bar()。我完全没有触及 foo() 实现。
但缺点是我正在使用 MockSomeClass 实例来测试 SomeClass 的 foo() ].这是我不喜欢的
上面的问题有没有更好的解决办法?
备注:
- 我不是在这里谈论依赖注入。依赖注入是完全不同的方法。
- 我在 UIViewController. 中测试 UI 代码时遇到这类问题
- 我想到了基于协议的编程,但无法提出解决上述问题的方法。
因此,您想测试一个方法 (foo
) 调用或不调用另一个方法 (bar
)。 foo
方法是被测的,bar
方法是广义上的依赖组件。
如果 bar
的调用有持久的副作用,您可以测试副作用 is/isn 不存在,也许通过查询 属性 或类似的。在那种情况下,您不需要模拟或类似的东西。
如果没有副作用,那么您必须替换依赖项。为此,您需要一个 seam,您可以在其中放置可以告诉测试是否已调用该方法的代码。为此,我只能看到 Jon 已经在
您可以将这两种方法放在单独的 class 中,在这种情况下 class 边界就是接缝。使用一个协议,或者只是一个非正式的约定,你可以完全替换实现 bar
的 class。依赖注入在这里派上用场。
如果这两个方法必须保持相同 class 那么您必须使用子class 边界作为接缝,即您可以使用可以覆盖方法并实现 test-specific sublass。当您可以使用模拟框架时,这是最简单的。如果那不是一个选项,您必须自己编写代码,就像您在问题中描述的那样。