参数化测试

Run Tests License codecov

参数化测试允许您轻松创建和运行动态的、运行时测试,这些测试会测试来自给定数据集的每个值的组合。 通过此库,您可以快速轻松地验证代码在各种输入值下的行为,从而确保代码正确且健壮。

目录

安装

Xcode

如果使用 Xcode,请将 https://github.com/cameroncooke/SwiftParameterizedTesting.git 添加到项目设置中的 Package Dependencies 列表中。

SwiftPM

如果想在使用 SwiftPM 的项目中使用 SwiftParameterizedTesting,请将该软件包作为 Package.swift 中的依赖项添加。

dependencies: [
    .package(url: "https://github.com/cameroncooke/SwiftParameterizedTesting.git", from: "0.1.3")
]

接下来,将 ParameterizedTesting 添加为测试目标的依赖项

targets: [
  .target(name: "MyLibrary"),
  .testTarget(
    name: "MyLibraryTests",
    dependencies: [
      "MyLibrary",
      .product(name: "ParameterizedTesting", package: "SwiftParameterizedTesting"),
    ]
  )
]

演示

Demo

什么是参数化测试?

参数化测试是一种测试类型,其中使用不同的输入值多次运行相同的测试。 这允许测试人员验证软件是否在各种输入值下正确运行,而无需为每个单独的值手动创建单独的测试用例。 这可以通过避免编写和维护许多单独的测试用例来帮助节省时间和精力。

如果任何排列组合失败,我最终不会在 Xcode 中遇到单个测试失败吗?

不,这就是奇迹发生的地方,ParameterizedTesting 将为每个值的组合动态创建单独的运行时测试,以便您确切知道哪些测试通过或失败。 当您运行测试套件时,每个值组合的测试都会出现在 Xcode 测试导航器中。

哪些用例适合参数化测试?

这种测试自动化在快照测试中尤其有用,在快照测试中,您希望确保视图的每个配置都有一个快照表示,其中存在许多排列组合,但它也可用于逻辑测试。

有什么警告?

请谨慎使用此库! 只需几行代码,就很容易最终得到数千个运行时测试。 请注意,测试套件的大小会随着每个附加的值集呈指数增长。

    override class func values() -> ([WeatherData.Weather], [CelsiusTemperature]) {
        (
            [.raining, .sunny, .cloudy, .snowing],
            [12, 34, 3, 22, 0]
        )
    }

上面是一个简单的测试值集合,分别是 4 个值和 5 个值的两个数组。 仅此测试就将生成 4 * 5 == 20 个测试。

现在让我们看一个更大的测试数据集

    override class func values() -> (
        [String],
        [CelsiusTemperature],
        [String],
        [String],
        [Double],
        [String],
        [String],
        [String],
        [Bool]
    ) {
        (
            [
                "raining",
                "sunny",
                "cloudy",
                "snowing",
            ],
            [
                12,
                34,
                3,
                22,
                0,
            ],
            [
                "apples",
                "oranges",
            ],
            [
                "red",
                "blue",
            ],
            [
                12.99,
                18.50,
            ],
            [
                "GB",
                "EU",
                "FR",
                "US",
            ],
            [
                "large",
                "small",
            ],
            [
                "red",
                "blue",
            ],
            [
                true,
                false,
            ]
        )
    }

上面是一个更大的测试值集合,分别是 4、5、2、2、2、4、2、2、2 个值的 9 个数组。 此测试将生成 4 * 5 * 2 * 2 * 2 * 4 * 2 * 2 * 2 == 5120 个测试!

重要的是,在使用参数化测试时,您需要真正考虑您正在创建的测试的价值,并明智地使用它们。 即使您可以测试每个组合,也并不意味着您应该这样做,而且通常情况下,您不应该这样做。

使用示例

快照测试

在您的测试目标中创建一个新的 Swift 文件,并继承其中一个 ParameterizedTestCase 基类。 假设您想从两组数据创建测试排列组合,您将使用 ParameterizedTestCase2 基类,如下例所示。 您总共可以使用最多 9 个数据集,只需使用相应的类名,并注意数字后缀,即 ParameterizedTestCase9

final class MySnapshotTests: ParameterizedTestCase2<Weather, CelsiusTemperature, Void> {

    override class func values() -> ([Weather], [CelsiusTemperature]) {
        (
            [.raining, .sunny, .cloudy, .snowing],
            [12, 34, 3, 22, 0]
        )
    }

    override func testAllCombinations(
        _ weather: Weather,
        _ temperature: CelsiusTemperature,
        _ expectedResult: Void?
    ) {
        let view = WeatherView(
            viewModel: WeatherView.ViewModel(
                weather: weather,
                temperature: temperature,
            )
        )

        assertSnapshot(
            matching: view,
            testName: "\(weather)_\(temperature)"
        )
    }
}

这些类使用了泛型,您必须在定义类时定义每组值的类型。 在上面的示例中,每个数据集的类型都定义为 <Weather, CelsiusTemperature, Void>Void 泛型参数是预期值的占位符,仅在创建逻辑测试时才需要。 对于快照测试,不需要它,所以我们在这里将其设置为 void。

接下来只需覆盖 testAllCombinations() 方法,当您使用 Xcode 时,Xcode 会自动完成此操作,并正确键入参数。 在您的方法中,只需添加使用注入值执行您想要的任何测试操作的测试逻辑即可。

逻辑测试

另一个有效的用例是逻辑测试。 在编写逻辑测试时,您可能需要根据预期值检查注入的值。

final class MyLogicTests: ParameterizedTestCase2<Weather, CelsiusTemperature, String> {

    override class func values() -> ([WeatherData.Weather], [CelsiusTemperature]) {
        (
            [.raining, .sunny],
            [12, 34, 3, 22, 0]
        )
    }

    override class func expectedValues() -> [String] {
        [
            "It's raining and a mild 12 degrees celsius",
            "It's raining and a hot 34 degrees celsius",
            "It's raining and a cold 3 degrees celsius",
            "It's raining and a comfortable 22 degrees celsius",
            "It's raining and a freezing 0 degrees celsius",

            "It's sunny and a mild 12 degrees celsius",
            "It's sunny and a hot 34 degrees celsius",
            "It's sunny and a cold 3 degrees celsius",
            "It's sunny and a comfortable 22 degrees celsius",
            "It's sunny and a freezing 0 degrees celsius",
        ]
    }

    override func testAllCombinations(
        _ weather: Weather,
        _ temperature: CelsiusTemperature,
        _ expectedResult: String?
    ) {
        let sut = WeatherData(weather: weather, temperature: temperature)
        XCTAssertEqual(sut.summary, expectedResult)
    }
}

在上面的示例中,与快照测试示例不同,我们需要提供第三个泛型类型,该类型表示预期值的类型,而不是指定 Void,我们指定了 String

testAllCombinations() 方法中,我们可以执行被测系统 (sut),并使用注入的 WeatherCelsiusTemperature 值提供 WeatherData 模型。

然后,我们断言生成的 "summary" String 与预期的结果 String 匹配。

完整的示例可以在 Tests/ExampleTests 中找到

自定义测试名称

默认情况下,每个运行时测试的名称将是 test_,后跟每个值的下划线分隔字符串表示形式。 如果任何值不符合 CustomStringConvertible,则将使用调试描述,这很可能是不希望的。 在这种情况下,您可以覆盖类方法 class func testName(_:),并为给定的测试值创建您自己的唯一名称

    override class func testName(_ value1: WeatherData.Weather, _ value2: Int) -> String {
        "weather_\(WeatherData.Weather)_and_\(value2)_degrees"
    }
}

请注意,您不需要提供 test_ 前缀,因为这将附加到从 class func testName(_:) 返回的任何值。

鸣谢

这个库部分来自 https://github.com/approvals/ApprovalTests.Swift

许可证

此库是在 MIT 许可证下发布的。 有关详细信息,请参阅 LICENSE