在XCode中执行单元测试,以多种语言测试本地化

时间:2017-06-27 18:01:45

标签: ios swift xcode unit-testing localization

我有一个使用多种不同语言的应用,我的目标是让单元测试能够验证翻译的文本是否符合我们的源材料。

到目前为止,我已经尝试过&阅读以下内容并未取得巨大成功:

这部分成功,但问题是语言更改在下次启动应用程序之前不会生效。我可以忍受这个,但是希望有更多的控制权,这样我就不必启动测试,然后重新启动以测试我想要的语言。

我在命令行构建中阅读了这篇Apple文档,看起来很有希望,因为我可以指定一个方案。我复制了我的应用程序,并将Run下的语言修改为西班牙语,如下所示:

enter image description here

但是,在Test下,我可以选择使用参数&环境变量在Run中设置,但没有关于使用其Options

enter image description here

在运行语言设置为西班牙语的情况下,在我修改的方案下启动应用程序,但遗憾的是我没有获得西班牙字符串。

最后,我知道在Test / Options下有一个选项可以显示非本地化的字符串,但这并不能解决我的问题。它们是否正确。

enter image description here

要进行设备测试非常耗时,因为需要触发产生错误的各种边缘情况,这就是我希望单元测试为我做的原因。

2 个答案:

答案 0 :(得分:4)

现在,借助Xcode 11中的测试计划,您可以为每个测试定义不同的配置。因此,请定义不同的配置,以便每个配置都使用特定语言启动应用程序。如果Application Language中未显示诸如波斯语之类的特定语言,则在Arguments Passed On Launch中添加其语言代码,如下所示:

-AppleLanguages (fa)

答案 1 :(得分:2)

我不确定直接回答你的问题所以我会就如何解决你的问题提供一些额外的想法。我使用下面的结构来测试我的所有字符串本地化,但只测试它们解析为有效的字典条目,而不是该字典条目实际包含的内容。但是,我相信你可以毫不费力地添加额外的支票。

这仅适用于Swift

我不再使用NSLocalizedString。相反,我使用本地化的.plist(“Strings.plist”,因为缺少更好的名称)文件和String的扩展名的组合,因此:

extension String {
var localized: String {
    return EKTLocaliser.sharedInstance.localize(string: self)
}

}

上面引用的EKTLocaliser对象如下所示:

final class EKTLocaliser: NSObject {

static let sharedInstance : EKTLocaliser = {
    let instance = EKTLocaliser()
    return instance
}()

private var localizableDictionary: NSDictionary

private override init() {
    self.localizableDictionary = [:]
}

func setDictionary(lDict: NSDictionary) {
    self.localizableDictionary = lDict
}

func localize(string: String) -> String {
    guard let localizedString = ((localizableDictionary.value(forKey: string) as AnyObject).value(forKey: "value") as? String) else {
        assert(true, "Missing translation for: \(string)")
        return "--"
    }
    return localizedString
}

}

此本地化类要求.plist文件中的每个条目都是带有“value”键的字典(还有一个“注释”键,您可以在其中为翻译器添加注释,但本地化对象不依赖于评论)

以下是Base(.plist)字典中一个条目的示例:

Base localisation example entry

在您的应用程序代码中,您可以使用此构造(取决于,见下文):

myLabel.text = someTextString.localized

现在谈谈单元测试。在单元测试中,您可以实例化本地化程序的实例,将.plist字典加载到其中,并且您可以完全访问所有本地化字符串。您加载的特定.plist将取决于您要测试的本地化。您可以从主捆绑中获取项目中所有本地化的列表:

       self.availableLanguages = Bundle.main.localizations

然后,使用该列表,依次加载每个本地化字典(和测试):

        if let dict = loadDictionary(root: self.availableLanguages[i]) {
        self.stringLocaliser?.setDictionary(lDict: dict)
    }
    else {
        XCTFail("Cannot load Base localisation dictionary")
    }

其中loadDictionary是:

    func loadDictionary(root: String) -> NSDictionary? {
    if let path = Bundle.main.path(forResource: root + ".lproj/Strings", ofType: "plist") {
        return NSDictionary(contentsOfFile: path)
    }
    else {
        assertionFailure("Cannot instantiate path for Strings.plist")
    }
    return nil
}

最后,我将所有键的枚举常量创建到本地化字典中,并迭代枚举以测试所有字符串。我做的唯一测试是验证本地化解析为有效的字典条目,但你可以扩展它以测试它解析为你期望的(即正确的翻译):

enum Ls:String {
case kLearnShu              = "shu"
case kLearnHa               = "ha"
case kLearnRi               = "ri"
case kLearnExercise1        = "ex1"
case kLearnExercise2        = "ex2"
case kLearnExercise3        = "ex3"

}

这种枚举方法的不幸之处在于它在您的代码中是侵入性的。而不是写

mLabel.text = kLearnExercise1.localized

你必须写:

myLabel.text = Ls.kLearnExercise1.rawValue.localized

这是一种不幸的痛苦。也许有更好的方式......

啊,最后一点......迭代一个字典是一个小小的位置,但我在SO上使用其他帖子的代码。因为它只是测试代码而且从未出现在您的应用程序本身中,所以我可以使用它。

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
var i = 0
return AnyIterator {
    let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
    if next.hashValue != i { return nil }
    i += 1
    return next
}

}

希望能给你一些想法......

对所有SO帖子的致谢和尊重,我将这种方法拼凑起来......