在运行时更改iOS模拟器的当前语言环境

14

在开发一组日期计算和语言规则来将数字值和日期转换为字符串时,我正在编写测试用例来断言字符串格式化方法的输出结果。一个虚构的断言可能看起来像这样:

在编写测试时,我正在开发一组日期计算和语言规则,用于将数字值和日期转化为字符串,并编写测试用例来验证字符串格式化方法的输出结果。

NSAssert([dateString isEqualToString:@"Three days, until 6:00 PM"], @"Date string should match expectation");

但是,由于该应用程序已本地化为多种语言,并且我的其他开发人员也来自不同的区域设置,可能会出现您的设备或模拟器与编写测试所用的区域设置不同的情况。在这种情况下,dateString 的内容可能是这样的:

@"Drie dagen, tot 18:00" // the assertion fails
@"Drei Tage, bis 18 Uhr" // the assertion also fails

这可能或可能不是这些区域设置的正确日期注释,但我的问题是如何能够在使用类似于以下代码的Apple API时运行特定地区设置的测试:

[NSDateFormatter localizedStringFromDate:date                  dateStyle:NSDateFormatterNoStyle                  timeStyle:NSDateFormatterShortStyle];

我希望我的论述可以涉及到两种或更多语言,就像这样:

[NSSomething actionToSetTheLocaleTo:@"en_US"];
dateString = ...; // the formatting
NSAssert([dateString isEqualToString:@"Three days, until 6:00 PM"], @"match en_US");

[NSSomething actionToSetTheLocaleTo:@"nl_NL"];
dateString = ...; // the formatting
NSAssert([dateString isEqualToString:@"Drie dagen, tot 18:00"], @"match nl_NL");

谁知道如何实现这个效果?

注:

  • 更改首选语言不够,还需要影响NSDateFormatter和NSNumberFormatter的行为。
  • 由于这只是用于单元测试目的,我可以使用私有API。但是,为了其他人的利益,公共API更好。
  • 传递自定义区域设置到每个日期或数字格式化API可能是最后的考虑,但我发布此问题希望避免采取这些极端措施。如果您确实知道这是唯一的解决方案,请提供一些参考文献,我将不再浪费时间。

有关此主题的链接:


4
你是否尝试过使用方法交换来提供自己实现的+[NSLocale currentLocale]?当然,只有在NSDateFormatter也内部使用此方法时才能起作用。 - Desmond
@Desmond,太棒了!我还没有尝试过,是的,NSDateFormatter类方法确实使用autoupdatingCurrentLocale和currentLocale方法,并且swizling可行!如果您能将此作为答案发布到此问题中,我将标记并添加我正在测试的类别方法以获得清晰度。谢谢! - epologee
@Desmond,您还有22个小时来领取悬赏。赶快行动并添加答案! - epologee
2
糟糕,我没有及时回来查看。不过没关系,很高兴能帮助到你! - Desmond
发生了。谢谢! - epologee
3个回答

19
< p > @Desmond指出了一种有效的解决方案。在他将答案放在这里之前,让我总结一下我最终使用了哪些代码。

事实证明,解决方案“很容易”,只需交换类方法内部使用的方法即可:

beforeEach(^{
    [NSBundle ttt_overrideLanguage:@"nl"];
    [NSLocale ttt_overrideRuntimeLocale:[NSLocale localeWithLocaleIdentifier:@"nl_NL"]];
});

afterEach(^{
    [NSLocale ttt_resetRuntimeLocale];
    [NSBundle ttt_resetLanguage];
});

您在上面看到的ttt_...方法会使用NSObject、NSLocale和NSBundle上的类别,在运行时检查它是否应该使用原始方法或返回其他内容。该方法在编写测试时运行得非常出色,虽然它从技术上讲没有使用任何私有API,但我强烈建议仅在您的测试设置中使用它,而不是用于您提交给App Store进行审核的任何内容。

在这个代码片段中,您将找到我添加到我的应用程序的Objective-C类别,以实现所需的行为。


非常棒的回答,GIST 非常有帮助。 - Mike
很高兴能够帮助 :) - epologee
您,先生,是一个大神! :) 这里有很多答案,但没有一个像您这样做得最好 :) 干杯! - Mihai Fratu
它能在Swift中运行吗?因为我正在考虑重写它。 - mkkrolik
Swizzling在Swift中不存在。在Swift早期,它可以工作,因为DateFormatter是一个桥接的Objective-C类。我不知道Swift和Objective-C之间的当前桥接状态,但也许这种方法不再起作用了。你试过了吗? - epologee

3
由于这是一个单元测试,您是否尝试在NSDateFormatter上设置语言环境?您不必在整个模拟器上设置语言环境,可以将其作为测试的参数。大多数依赖于语言环境的Cocoa方法都可以将其作为参数或属性传递。
类似于以下内容:
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en-US"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setLocale:locale];
[formatter setDateStyle: NSDateFormatterNoStyle];
[formatter setTimeStyle: NSDateFormatterShortStyle];
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
thing = [formatter stringForObjectValue:date];

你正在使用的localizedStringFromDate:dateStyle:timeStyle:方法在这里有详细说明,除了区域设置(locale)以外,其它都很清晰明了。因此,你可以按照相同的步骤操作,只需将区域设置(locale)设为与系统当前区域设置不同的值即可。

请注意,这不会更改模拟器的语言环境,而是展示了如何为您正在测试的内容设置语言环境。 - quellish
是的,这将是我的备选方案。虽然这是一个现有的日历应用程序,但日期和数字格式化程序的类方法被使用了很多地方。但我希望能够避免改变它们所有... - epologee

2

我认为唯一的方法是quellish提到的。不过你提到了这种情况在很多地方都存在。

为了避免重写所有当前代码,在你的pch文件中,你可以做一些花哨的事情,比如

#import "UnitTestDateFormatter.h"
#define NSDateFormatter UnitTestDateFormatter

然后,只需创建一个子类来处理它:

@implementation UnitTestDateFormatter

- (id) init
{
    self = [super init];

    if(self != nil)
    {
        [self setLocale:...];
    }

    return self;
}

@end

至少这样你的代码可以保持不变。


有趣的黑客技巧。我从未使用过宏定义来窃取内置类名,但是为了测试,这似乎是值得探索的事情。然而,没有必要测试NSDateFormatter的行为,而是我对项目格式化类组成的好奇。感谢分享。 - epologee
关于 #define 需要知道的是它在编译时运行,并且类似于查找和替换。在 "#define XXX YYY" 中,将取代您代码中所有 XXX 的部分为 YYY。当然,这仅适用于导入和使用了 #define 的类。将其放在像我提到的 pch 文件中会影响所有源文件(而不是现有的二进制文件),因为 pch 文件由所有源文件自动导入。 - SomeGuy
我意识到的是,在.h文件中,#define也会替换正确的子类NSDateFormatter,因此它将变成类似@interface UnitTestDateFormatter : UnitTestDateFormatter的东西。如果您想使用它,可能需要在类声明之前使用#undef,然后在底部再次使用#define。 - SomeGuy
为什么不编辑您的答案以包含此信息,而不是在评论中放置它? - epologee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接