首页 \ 问答 \ 嘲笑斯威夫特(Mocking with Swift)

嘲笑斯威夫特(Mocking with Swift)

考虑以下用Swift编写的名为SomeClass的类:

@objc class SomeClass: NSObject
{
    var shouldCallBar = false

    func foo()
    {
        if (shouldCallBar == true)
        {
            bar()
        }
    }

    func bar()
    {
    }
}

为了测试上面的类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与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)
    }
}

请注意,我正在对正在测试的原始类进行子类化并覆盖bar() 。 我根本没有触及foo()实现。

但缺点是我使用MockSomeClass实例来测试SomeClass的 foo() 。 这是我不喜欢的, 也不推荐

有没有更好的解决方案来解决上述问题?

笔记:

  • 我不是在谈论依赖注入。 依赖注入是完全不同的方法。
  • 在UIViewController中测试UI代码时,我遇到了这些问题。
  • 我已经考虑过基于协议的编程,但无法提出上述问题的解决方案。

Consider the following class named SomeClass written in Swift:

@objc class SomeClass: NSObject
{
    var shouldCallBar = false

    func foo()
    {
        if (shouldCallBar == true)
        {
            bar()
        }
    }

    func bar()
    {
    }
}

For testing the above class foo() method (and similar scenarios mostly written in Objective-C) I was using OCMock like:

- (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];
}

But above test fails with Swift code as OCMock won't works well with Swift.

So I am considering something like entirely in 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)
    }
}

Note here I am subclassing the original class under test and overriding the bar(). I am not at all touching the foo() implementation.

But the downside is I am using MockSomeClass instance to test foo() of SomeClass. This is something I don't like and not recommended.

Is there any better solution to the problem above?

Notes:

  • I am not talking about Dependency Injection here. Dependency Injection is entirely different approach.
  • I face these kind of issues when testing UI code in UIViewController.
  • I have thought of Protocol based programming but was not able to come up with solution to problem above.

原文:https://stackoverflow.com/questions/36802678
更新时间:2019-12-07 13:04

最满意答案

所以,你想测试一个方法( foo )是否调用另一个方法( bar )。 foo方法是被测试的方法,而bar方法在更广泛的意义上是依赖组件。

如果调用bar具有持久的副作用,您可以通过查询属性或类似物来测试副作用是否存在。 在这种情况下,你不需要嘲笑或类似。

如果没有副作用,那么你必须替换依赖。 为此,您需要一个接缝 ,您可以在其中放置可以告诉测试该方法是否已被调用的代码。 为此,我只能看到Jon在您提到问题中已经讨论过的两个选项。

您可以将两个方法放在单独的类中,在这种情况下,类边界是接缝。 使用协议或非正式约定,您可以完全替换实现bar的类。 依赖注入在这里派上用场。

如果这两个方法必须保持在同一个类中,那么您必须使用子类边界作为接缝,即您使用的事实是您可以覆盖方法并实现特定于测试的子类 。 当你可以使用模拟框架时,这是最简单的。 如果那不是一个选项,你必须自己编写代码,就像你在问题中描述的那样。


So, you want to test that one method (foo) does or does not call another method (bar). The foo method is the one under test, and the bar method is, in the wider sense, a dependent component.

If the invocation of bar has lasting side effects, you could get away with testing that the side effect is/isn't present, maybe by querying a property or similar. In that case you don't need mocks or similar.

If there are no side effects then you must substitute the dependency. To do so you need a seam at which you place code that can tell the test whether the method has been invoked or not. For that, I can only see the two options that Jon already discussed in the question you refer to.

You either put the two methods into separate classes, in which case the class boundary is the seam. Using a protocol, or just an informal convention, you can then completely replace the class that implements bar. Dependency injection comes in handy here.

If the two methods must stay in the same class then you have to use a subclass boundary as a seam, i.e. you use the fact that you can override methods and implement a test-specific sublass. It's easiest when you can use a mock framework. If that's not an option you have to write the code yourself, much like what you describe in your question.

2017-05-23

相关文章

更多

最新问答

更多
  • 删除不适用于JSP中使用for循环的每个id(Deletion not working for every id using for loop in JSP)
  • 如何从std :: filesystem :: path中删除引号(How to remove quotation marks from std::filesystem::path)
  • 验证多个控制器方法的URL路径(Validate URL path for several controller methods)
  • 如何在datarow []中的列中找到最大值?(How to find max value in a column in a datarow[] ?)
  • 如何使用预定义文本替换来自数据库的部分结果(How do I replace part of result coming from Database with predefined text)
  • Selenium Java注入了新的Javascript函数(Selenium Java inject new Javascript function)
  • 使用.on的多个下拉菜单选择文本仅适用于第一个下拉列表(Multiple Dropdowns Menu Selection text using .on works only on first dropdown)
  • 快速将黄土曲线添加到大型数据集图中的方法(Quick way to add loess curve to large data set graph)
  • FilteringSelect in mvc(FilteringSelect in mvc)
  • 在Delphi XE2中开发Mac或iOS应用程序需要哪些硬件/软件?(What hardware/software is necessary to develop Mac or iOS apps in Delphi XE2?)
  • 在原型的构造函数中初始化属性时获取“未定义”(Getting 'undefined' when a property is initialized in the constructor of a prototype)
  • 通过越狱加载的应用程序的Documents文件夹位置(Location of Documents folder for an app loaded via jailbreak)
  • 在OpenGL中使用可编程和固定管道功能(Using both programmable and fixed pipeline functionality in OpenGL)
  • 将任何用户输入重定向到单独的底层程序(redirect any user input to a separate underlying program)
  • 编辑文本不能正常工作android(Edit texts not working properly android)
  • “user_denied”Facebook应用页面上的Facebook用户区域设置(Facebook user locale on “user_denied” facebook app page)
  • 在大图像中找到小的部分透明图像的坐标(find coordinates of small partially-transparent image within a large image)
  • 我如何在cakephp 3.1中获得完整的相对路径?(How i can get full relative path of image in cakephp 3.1?)
  • 如何保存拖动标记的新本地化?(How to save new localization of dragged marker?)
  • MySQL UPDATE vs INSERT和DELETE(MySQL UPDATE vs INSERT and DELETE)
  • 在执行查询之前,在SQLAlchemy模型中将datetime转换为unix时间戳?(Convert datetime to unix timestamp in SQLAlchemy model before executing query?)
  • OpenCL与OpenGL互操作的优势(Advantage of OpenCL interoperability with OpenGL)
  • 如何解析用点和等分隔的数据然后添加到listview(How to parsing data from delimited with dot and equal then add to listview)
  • 带调试输出的X3解析器段错误(BOOST_SPIRIT_X3_DEBUG)(X3 parser segfaults with debug output (BOOST_SPIRIT_X3_DEBUG))
  • 将文件夹名称添加到fgrep结果(Add folder name to fgrep result)
  • 在MySQL中加载一个表是非常慢的(Loading one table in MySQL is ridiculously slow)
  • 如何将JSON放入PHP变量?(How do I put JSON into a PHP Variable?)
  • 如何绕过Microsoft.Speech.Recognition中的不流畅?(How to bypass disfluencies in Microsoft.Speech.Recognition?)
  • 原点的最后一行是什么?(What is the last row of an origin for?)
  • 将字符串转换为javascript中的操作(Translate String to operation in javascript)