【问题标题】:How to Unit Test method that calls NSDate()?如何对调用 NSDate() 的方法进行单元测试?
【发布时间】:2015-03-06 07:00:42
【问题描述】:

我目前正在使用 Swift。

我的问题很简单,如何对在数据库中以 NSDate() 作为属性创建对象的方法进行单元测试?

问题在于,在我的断言中,我将像这样创建一个新的 NSDate():

let expectedObject = Object(data: NSDate())
XCTAssertEqual(database.objects(), [expectedObject])

并且断言将失败,因为 2 个 NSDate 略有不同。

谢谢。

【问题讨论】:

  • 如何将所有对 nsdate 的调用存根?
  • 对于 kiwi,它会像 [ [dateObject should] receive:@selector(currentGear) andReturn:theValue(3)];有不同的模拟框架,或者你可以使用方法调配
  • Swift 不支持这些选项。

标签: ios unit-testing swift nsdate


【解决方案1】:

测试使用 Date()(或 CLLocationManager、ReachabilityManager 或其他任何静态方法)的方法的一个好方法是为您的整个应用设置一个可以在测试中模拟的环境。

为此,请创建一个 Environment 类,如下所示:

struct Environment {

    var date: () -> Date = Date.init
}

var Env = Environment()

在您应用的其余部分中,通过Env.date() 访问当前日期,而不是调用Date()

在您的测试中,创建一个像这样的模拟环境:

extension Environment {

    static let mock = Environment(
        date: {
            return Date(year: 2019, month: 11, day: 12, hour: 2, minute: 45, second: 0, millisecond: 0, timeZone: TimeZone.init(abbreviation: "CST"))
        }
    )
}

在测试设置中,将应用环境替换为模拟环境,如下所示:

override func setUp() {

    super.setUp()

    Env = .mock
}

我建议将它放在一个基类中,您的所有测试类都将从该基类中继承,这样每个测试都将使用模拟环境。

现在,当您运行您的应用时,您将看到当前日期,但在测试中,该日期将始终返回为 2019 年 11 月 12 日 02:45,因此您可以从该已知日期验证您想要的任何内容。

对于此设置的工作示例,我创建了一个小演示项目here。 Environment.swift、MockEnvironment.swift、DateExamplesViewController.swift 和 DateTests.swift 是相关文件。

【讨论】:

  • 只是说:我期望在 iOS 或 MacOS 上对 Date() 的两次调用相隔一秒通常会返回相隔一秒的日期(尽管不能保证)。您的代码将始终返回相同的日期。在这种情况下,很多代码都会中断。
  • @gasher729 如果您在操场上运行该代码,您会看到 Env.date() 在每次调用时返回不同的日期。当您将 Env 替换为 .mock 时,Env.date() 将在每次调用时返回相同的日期,这是预期的行为。如果您的测试需要唯一的日期,那么您就不想使用模拟环境。
【解决方案2】:

正如https://testdriven-ios.com中指出的那样

一个很好的方法是覆盖测试用例中的 NSDate.date 强制它返回一个已知的日期

@implementation NSDate (custom)

+ (instancetype)date{
    NSDateFormatter* formatter = NSDateFormatter.new;
    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZ"];
    return [formatter dateFromString:@"2017-12-28 13:00:10+0000"];
}

@end

然后在调用 NSDate.date 的任何地方,都会返回 2017-12-28 13:00:10+0000

@implementation NSDate (custom)

+ (instancetype)date{
    NSDateFormatter* formatter = NSDateFormatter.new;
    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZ"];
    return [formatter dateFromString:theDate];
}

@end

您甚至可以使用静态date 变量进一步自定义它,然后在测试中对其进行修改

-(void) test_something_out {
 theDate = @"2017-12-28 13:00:10+0000";
 //Test any function that uses nsdate.date
}

【讨论】:

    【解决方案3】:

    如果你能像这样使用Date.now(),实现起来会非常简单和干净

    extension Date {
        static var now: (()->Date) = {
            return Date()
        }
    }
    
    print(Date.now())
    Date.now = { Date().addingTimeInterval(43534543) }
    print(Date.now())
    

    【讨论】:

      【解决方案4】:

      一种简单的解决方法是使用以下方法:

      1. 创建一个从被测类派生的模拟类。
      2. 在被测类中添加类似“getDate()->NSDate”的方法, 返回“NSDate()”- 或任何预期值。
      3. 覆盖 模拟类中的 getDate()。您还可以在模拟中创建一个变量 类,从 getDate() 返回。有了这个,你就可以 提供您想要的测试日期。
      4. 最后,不要创建您希望测试的类的实例, 创建 测试中的模拟类实例。

      【讨论】:

      • 这是一个非常糟糕的解决方案,我不应该为了测试它而改变我的班级。
      • @RodrigoRuiz 他不是说要改变你的课程——他是说要创建一个正在测试的模拟课程。有时,模拟是有效测试的唯一方法。
      • Mocks 旨在替换依赖项,而不是替换您要测试的类。这样您就不是在测试您的课程,而是在测试模拟。
      • 对于日期,你真的想要一个模拟类。例如,为了确保您的代码在 12 月 31 日和 1 月 1 日正常工作(这段时间会发生很多错误)。
      【解决方案5】:

      NSDate.date (Objective C)、Date() (Swift)、Date.now() 在其他语言中都是有问题的,因为它们应该在不同的语言上给出不同的结果来电。

      因此,如果我将一个具有当前日期的对象添加到数据库中,并检查当我从数据库中读取它时它是否具有当前日期,那将无法正常工作。不是因为数据库或 Date.now() 函数有什么问题,而是因为您使用它的方式。

      模拟 Date.now() 总是返回相同的日期以使您的测试工作从根本上是有缺陷的,因为模拟的 Date.now() 不能按照它应该的方式工作!连续两次调用应该返回不同的日期,而不是同一个!

      正确的方法:创建一个日期对象 D,任何你喜欢的方式。添加一个日期为 D 的对象,将其写入数据库,从数据库中读取,检查对象的日期是否为 D。然后检查数据库文档中有关精度的内容:如果它将日期存储为秒,并且您以毫秒为单位存储日期,然后更改对象的定义(“此对象将所有日期四舍五入到整秒”),或者您接受写入+读取返回日期略有不同的对象,并相应地更改单元测试.

      因此,您要做的是记录您期望的行为,让单元测试测试预期的行为,并在失败时确定单元测试是否错误,或者您的代码错误,或者预期的行为应该不同。 (单元测试也可能有错误,预期行为的规范也可能有错误)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多