有没有办法使用参数化单元测试,类似于使用NUnit框架在.Net中可以实现的。
[TestCase(12, 3, 4)]
[TestCase(12, 2, 6)]
[TestCase(12, 4, 3)]
public void DivideTest(int expectedResult, int a, int b)
{
Assert.AreEqual(expectedResult, a / b);
}
使用这种测试(与非参数化测试相比)可以让你避免编写一系列几乎相同的单元测试,而不仅仅是参数值。
我正在寻找基于XCTest的解决方案或其他一些方法来实现它。最佳解决方案应将每个测试用例(参数集)作为Xcode中的单独单元测试进行报告,因此是否所有测试用例或仅部分测试用例都失败了。
答案 0 :(得分:7)
你的功能参数到处都是。我不确定你的功能是做乘法还是除法。但是,这是一种可以在单个测试方法中执行多个测试用例的方法。
鉴于此功能:
func multiply(_ a: Int, _ b: Int) -> Int {
return a * b
}
您可以拥有多个测试用例:
class MyTest: XCTestCase {
func testMultiply() {
let cases = [(4,3,12), (2,4,8), (3,5,10), (4,6,20)]
cases.forEach {
XCTAssertEqual(multiply($0, $1), $2)
}
}
}
最后两个会失败,Xcode会告诉你它们。
答案 1 :(得分:5)
使用参数化的最佳方法是使用XCTestCase子类的属性defaultTestSuite
。下面是一个明显的分裂示例:
import XCTest
class ParameterizedExampleTests: XCTestCase {
//properties to save the test cases
private var array: [Float]? = nil
private var expectedResult: Float? = nil
// This makes the magic: defaultTestSuite has the set of all the test methods in the current runtime
// so here we will create objects of ParameterizedExampleTests to call all the class' tests methodos
// with differents values to test
override open class var defaultTestSuite: XCTestSuite {
let testSuite = XCTestSuite(name: NSStringFromClass(self))
addTestsWithArray([12, 3], expectedResult: 4, toTestSuite: testSuite)
addTestsWithArray([12, 2], expectedResult: 6, toTestSuite: testSuite)
addTestsWithArray([12, 4], expectedResult: 3, toTestSuite: testSuite)
return testSuite
}
// This is just to create the new ParameterizedExampleTests instance to add it into testSuite
private class func addTestsWithArray(_ array: [Float], expectedResult: Float, toTestSuite testSuite: XCTestSuite) {
testInvocations.forEach { invocation in
let testCase = ParameterizedExampleTests(invocation: invocation)
testCase.array = array
testCase.expectedResult = expectedResult
testSuite.addTest(testCase)
}
}
// Normally this function is into production code (e.g. class, struct, etc).
func division(a: Float, b: Float) -> Float {
return a/b
}
func testDivision() {
XCTAssertEqual(self.expectedResult, division(a: array?[0] ?? 0, b: array?[1] ?? 0))
}
}
答案 2 :(得分:3)
我更喜欢@DariusV的solution。但是,当开发人员直接从Xcode的侧边栏执行测试方法时,它不能很好地处理。对我来说这是个大问题。
我认为我所做的事情很圆滑。
我声明了Dictionary
中的testValues
(问题需要一个更好的名称)作为我的XCTestCase
子类的实例计算属性。然后,我定义了一个Dictionary
输入常量来期望期望的输出。我的示例测试了作用于Int
的函数,因此我像这样定义testValues
:
static var testRange: ClosedRange<Int> { 0...100 }
var testValues: [Int: Int] {
let range = Self.testRange
return [
// Lower extreme
Int.min: range.lowerBound,
// Lower bound
range.lowerBound - 1: range.lowerBound,
range.lowerBound : range.lowerBound,
range.lowerBound + 1: range.lowerBound + 1,
// Middle
25: 25,
50: 50,
75: 75,
// Upper bound
range.upperBound - 1: range.upperBound - 1,
range.upperBound : range.upperBound,
range.upperBound + 1: range.upperBound,
// Upper extreme
Int.max: range.upperBound
]
}
在这里,我很容易声明边缘和边界情况。实现此目的的另一种语义方式可能是使用元组数组,但是Swift的字典文字语法足够薄,我知道这是做什么的。 ?
现在,在我的测试方法中,我有一个简单的for
循环。
/// The property wrapper I'm testing. This could be anything, but this works for example.
@Clamped(testRange) var number = 50
func testClamping() {
let initial = number
for (input, expected) in testValues {
// Call the code I'm testing. (Computed properties act on assignment)
number = input
XCTAssertEqual(number, expected, "{number = \(input)}: `number` should be \(expected)")
// Reset after each iteration.
number = initial
}
}
现在可以为每个参数运行,我只需以Xcode的任何常规方式或与Linux兼容的任何其他方式(我假设)调用XCTests。无需运行每个测试类来获得该参数的正确性。
那不是很漂亮吗?我只在几行DRY代码中介绍了每个边界值和等价类!
对于识别失败的案例,每个调用都通过XCTAssert
函数运行,根据Xcode的约定,该函数仅在您需要考虑某些错误时才向您发送消息。这些内容显示在边栏中,但是类似的消息往往会融合在一起。我的消息字符串在此处标识了失败的输入及其结果输出,修复了混合在一起的问题,并使我的测试流程成为了一个理智的樱桃派。 (您可以按自己喜欢的方式格式化自己,碰碰碰!任何能使您保持理智的方式。)
美味。
对@Code Different的answer的改编:使用输入和输出字典,并运行for
循环。 ?
答案 3 :(得分:2)
@Code Different的回答是合法的。这是其他两个选项,或者更确切地说是解决方法:
您可以使用Fox之类的工具来执行生成性测试,其中测试框架将为您要测试的行为生成许多输入集并为您运行它们。
有关此方法的更多信息:
如果您喜欢BDD样式并使用支持它们的测试框架,则可以使用共享示例。
使用Quick看起来像是:
class MultiplySharedExamplesConfiguration: QuickConfiguration {
override class func configure(configuration: Configuration) {
sharedExamples("something edible") { (sharedExampleContext: SharedExampleContext) in
it("multiplies two numbers") {
let a = sharedExampleContext()["a"]
let b = sharedExampleContext()["b"]
let expectedResult = sharedExampleContext()["b"]
expect(multiply(a, b)) == expectedResult
}
}
}
}
class MultiplicationSpec: QuickSpec {
override func spec() {
itBehavesLike("it multiplies two numbers") { ["a": 2, "b": 3, "result": 6] }
itBehavesLike("it multiplies two numbers") { ["a": 2, "b": 4, "result": 8] }
itBehavesLike("it multiplies two numbers") { ["a": 3, "b": 3, "result": 9] }
}
}
说实话,这个选项是:1)很多工作,2)滥用共享示例技术,因为你没有使用它们来测试多个类共享的行为,而是参数化测试。但正如我在开始时所说,这是一个更多的解决方法。
答案 4 :(得分:0)
所有断言似乎都throw
,所以也许这样的事情对您有用:
typealias Division = (dividend: Int, divisor: Int, quotient: Int)
func testDivisions() {
XCTAssertNoThrow(try divisionTest((12, 3, 4)))
XCTAssertNoThrow(try divisionTest((12, 2, 6)))
XCTAssertNoThrow(try divisionTest((12, 4, 3)))
}
private func divisionTest(_ division: Division) throws {
XCTAssertEqual(division.dividend / division.divisor, division.quotient)
}
如果一个失败,则整个功能将失败。如果需要更多的粒度,则可以将每种情况分解为一个单独的函数。
答案 5 :(得分:0)
我们发现此解决方案How to Dynamically add XCTestCase为我们提供了所需的灵活性。能够动态添加测试,将参数传递给测试,并在测试报告中显示动态测试名称。