首页 \ 问答 \ 嘲笑斯威夫特(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

相关问答

更多

Swift使输入透明,只有border-bottom(Swift make input transparent with only border-bottom)

这是一个使用UIView而不是CALayer的替代实现。 let line = UIView() line.frame.size = CGSize(width: textField.frame.size.width, height: 1) line.frame.origin = CGPoint(x: 0, y: textField.frame.maxY - line.frame.height) line.backgroundColor = UIColor.darkGray line.autore

如何使用swift 4为用户默认数组添加值(How to add value to user default array with swift 4)

由于每次尝试在UserDefaults中设置值,它实际上会覆盖密钥的值。 你需要做的是: 在UserDefaults中读取现有数组 将新值附加到此数组 将此数组写回UserDefaults。 Since you try to set the value in UserDefaults each time, it actually overwrites the value for the key. What you need to do is : Read the existing array i

模拟一直在调用真正的功能(Mock keeps calling real function)

所以,在github上搜索并查看一些代码,我发现即使函数属于use_cases模块,我也需要从view进行模拟。 所以我现在的代码是: tests.py from unittest.mock import patch, Mock @patch('core.views.add_owner_to_place') @patch('core.forms.PlaceForm.is_valid') @patch('core.forms.PlaceForm.save') def test_save_shou

从1字典添加项目到另一个字典-Swift iOS9(Add items from 1 Dictionary to another Dictionary -Swift iOS9)

问题: self.foodDict = ["KFC": "Chicken", "PizzaHut": "Pizza", "McDonalds":"Burger"] 这一行创建新的数组并分配这些值。 解: 有一个暂时的财产,用于持有关键值[“肯德基”:“鸡肉”,“PizzaHut”:“披萨”,“麦当劳”:“汉堡”]。 迭代并将其分配给FoodDict。 例: //Button @IBAction func buttonPressed(sender: UIButton) { self.a

如何在Swift 3中将HexString转换为ByteArray(How do i convert HexString To ByteArray in Swift 3)

该代码可以生成与swift 2代码相同的输出。 func stringToBytes(_ string: String) -> [UInt8]? { let length = string.characters.count if length & 1 != 0 { return nil } var bytes = [UInt8]() bytes.reserveCapacity(length/2) var index = string

我无法让我的Swift 3定时器启动(I cannot get my Swift 3 Timer to fire)

(NS)Timer需要一个运行循环才能正常工作。 您可以通过编程方式将计时器添加到循环中,但使用该方法会更方便 Timer.scheduledTimer(withTimeInterval:... (NS)Timer needs a run loop to work properly. You can add the timer programmatically to the loop but it's more convenient to use the method Timer.schedu

使用Interface Builder中的swift全局变量(单例)(Use swift global variable (singleton) from Interface Builder)

编辑:正如评论中所讨论的,这仅适用于NSObject的子类。 在这个答案的时候,没有办法通过IB访问“纯粹的”Swift课程。 您可以将“对象”项拖到IB中,并为其指定一个Swift类。 从IB调色板中选择“对象”: 将其拖动到左侧边栏上的视图控制器下: 并在右侧的对象检查器中将其分配给您的类: Edit: As discussed in the comments, this works only if you subclass NSObject. At the time of this ans

快速连续旋转动画不那么连续(Swift Continuous Rotation Animation not so continuous)

我不确定你的代码有什么问题,但是我已经使用这种方法实现了连续旋转, @IBAction func rotateView(sender: UIButton) { UIView.animateWithDuration(0.5, delay: 0, options: .CurveLinear, animations: { () -> Void in self.spinningView.transform = CGAffineTransformRotate(se

相关文章

更多

最新问答

更多
  • asp.net任意用户信息(asp.net arbitrary user info)
  • 如何使用python计算docx文件中表中行的值(How to count the row's values in tables in docx file by using python)
  • MySQL:用户访问和数据库覆盖(MySQL: User access and DB overwriting)
  • 还有另一种“使用未分配的局部变量”的问题(Yet Another “Use of unassigned local variable 'whatever' ” question)
  • 开源证书颁发机构软件(Open source certificate authority software)
  • Rails中的迭代form_for是在create上添加模型的所有实例(Iteration in Rails form_for is adding all instances of model on create)
  • 如何扩展我的表视图单元格?(How to expand my table view cell?)
  • 如何使用SPARQL区分Thing和无生命对象(How to differentiate between a Thing and an inanimate object with SPARQL)
  • 在IdentityServer中,Client Secrets和Scope Secrets有什么区别?(In IdentityServer, what is the difference between Client Secrets and Scope Secrets?)
  • 如何在具有附加类时重写类(How do I override a class when it has a attached class)
  • 如何使用Git在Azure上部署C#,MVC4应用程序(How to deploy a C#, MVC4 application on Azure using Git)
  • Sitecore 7内容搜索爬网程序根目录之外的索引项(Sitecore 7 Content Search indexing items outside of crawler root)
  • 我应该在线课程使用utf-8编码吗?(Should I use utf-8 encoding for an online course?)
  • 如何在Cucumber-JS步骤定义中使用Node-mysql连接到MySQL?(How to connect to MySQL using Node-mysql in a Cucumber-JS step definition?)
  • 在MVC 4中的google.maps.LatLng(lat,lon)中将JSON字符串值分配给纬度和经度(Assign the JSON string value to Latitude and Longitude in google.maps.LatLng(lat,lon) in MVC 4)
  • awk:通过特定的分隔符删除字符串(awk: remove strings by specific delimiter)
  • 如何测试Vista的应用程序(How to test app for Vista)
  • Elasticsearch聚合器 - 缺失值的工作原理(Elasticsearch aggregators - How missing values work)
  • 绘制datetime.date熊猫(Plot datetime.date pandas)
  • PostgreSQL作为WSO2 EI和APIM + IS的数据源(PostgreSQL as datasource for WSO2 EI and APIM+IS)
  • 如何使用bash在postgres中运行alter table脚本(How to run alter table script in postgres using bash)
  • 可能使用PHP阻止整个美国州访问我的网站?(Might it be possible to block an entire US state from accessing my site, using PHP?)
  • restangular删除并输入错误网:: ERR_NAME_NOT_RESOLVED(restangular remove and put error net::ERR_NAME_NOT_RESOLVED)
  • 常见问题解答的Modx(Revolution)搜索功能(Modx(Revolution) search function for FAQs)
  • Rubymine如何使用远程口译员和Git?(How Does Rubymine Work With Remote Interpreters and Git?)
  • prepareForSegue和PerformSegueWithIdentifier发件人(prepareForSegue and PerformSegueWithIdentifier sender)
  • postgrsql与PowerShell无提示安装问题(postgresql silent installation issue with powershell)
  • 比较两个greps的输出(Comparing output from two greps)
  • 使用.NET RIA Data Services删除Silverlight 3中的数据(Deleting data in Silverlight 3 with .NET RIA Data Services)
  • 此行中AND运算符的含义(meaning of the AND operator in this line)