Objective C - 具有多个测试输入的单元测试的最佳实践
Objective C - Best practice for Unit Testing with multiple test inputs
我正在为现有项目编写单元测试代码。该项目在 Objective-C 中,我必须通过对测试用例的大量输入来测试几个函数。例如,我有一个测试用例来测试输入两个参数的函数计算器。目前我创建数组来将输入值集存储到 运行 测试。使用的代码如下:
- (void)setUp {
[super setUp];
self.vcToTest = [[BodyMassIndexVC alloc] init];
input1 = [[NSMutableArray alloc] initWithObjects:@"193", @"192", @"192", @"165", @"155", @"154", nil];
input2 = [[NSMutableArray alloc] initWithObjects:@"37", @"37", @"36", @"80",@"120", @"120", nil];
}
- (void)testCalculatorSuccess {
for (int i=0; i<input1.count; i++) {
NSArray *expectedResult = [[NSArray alloc] initWithObjects: @"9.93", @"10.04", @"9.77", @"29.38", @"49.95", @"50.60", nil];
NSString *actualResult = [self.vcToTest calculateResult:input1[i] andInput2:input2[i]];
XCTAssertEqualObjects(actualResult, expectedResult[i]);
}
}
我在网上搜索了最佳做法,但没有找到。有人可以帮我弄这个吗?我 运行 是否以正确的方式进行测试?在这种情况下应遵循的最佳做法是什么?我应该为每组输入创建一个测试用例吗?
您的测试 class 应该针对一个特定的事物进行测试,即 sut(被测系统)。在您的情况下,sut 变量应该是您的 vcToTest。我要进行测试的方法是通过依赖注入,而不是将您正在测试的所有数组存储为实例变量。因此,您将创建一个测试方法来接收要测试的参数。这样就不用一直创建实例变量了,只创建和测试方法相关的局部变量。
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
self.sut = [[BodyMassIndexVC alloc] init];
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
self.sut = nil;
[super tearDown];
}
- (void)testBMIFormulaInCmAndKgSuccess {
// local variables that are only seen in this method
NSArray *heightsInMetres = [[NSMutableArray alloc] initWithObjects:@"193", @"192", @"192", @"165", @"155", @"154", nil];
NSArray *weightsInKg = [[NSMutableArray alloc] initWithObjects:@"37", @"37", @"36", @"80",@"120", @"120", nil];
NSArray *expectedResults = [[NSArray alloc] initWithObjects: @"9.93", @"10.04", @"9.77", @"29.38", @"49.95", @"50.60", nil];
for (int i=0; i<heightsInMetres.count; i++) {
[self xTestHeightInMeters:heightsInMetres[i] weightInKg:weightsInKg[i] expecting:expectedResults[i]];
}
}
// the variables that are used to test are injected into this method
- (void)xTestHeightInMeters:(NSString *)height weightInKg:(NSString *)weight expecting:(NSString *)expectedResult {
NSString *result = [self.sut calculateBMIforHeight:height andWeight:weight];
XCTAssertEqual(result, expectedResult);
}
如果我是你,我不会为 运行 测试创建数组。数组很乱,变得很难理解发生了什么,也很容易出错。我会创建特定的测试方法来测试一件事以确保 sut 正常工作。例如,在 TDD 中,您创建一个会失败的测试方法,然后修改您的 sut 以最简单的方式修复此失败。通常这意味着您的修复会 return 完全符合您的预期。然后你做了另一个测试,用不同的值测试完全相同的东西,它现在应该失败,因为你的 sut 只是 returning 他们之前的测试正在寻找的东西。现在您再次修改您的 sut 以使两个测试都通过。在此之后,在大多数情况下您将不需要任何附加测试,因为它已被证明可以以两种独特的方式工作。
我知道您说过您正在测试已经编写好的软件,但我强烈建议您查看测试驱动开发。即使您实际上并不应用 TDD,您也会学习如何创建有意义的测试。 This helped me learn tdd
根据我的经验,关键考虑因素应该是随着时间的推移维护测试套件的难易程度。您当前的方法会在未来以两种方式引起问题:
如果您想使用不同的数字计算 BMI,则需要使用不同的测试 class(因为您在设置方法中锁定了这些值)。
如果您决定使用不同的数学或多个 BMI 方程式,则必须在检查这些值的任何地方更新数组。
我建议改为创建一个包含身高、体重和预期 BMI 值的 CSV 或纯文本文件。这样可以将测试数据保持在一起。然后在您的测试方法中,加载文件并根据预期 BMI 检查您的实际 BMI。
您可以在此处灵活地混合和匹配测试数据,或为不同的 BMI 方程使用不同的测试文件。就个人而言,我也喜欢这样一个事实,即您可以在更改内容时保留旧数据文件,以防您想要回滚或添加旧算法支持。
一个快速而肮脏的版本看起来像这样:
- (NSArray *)dataFromFileNamed:(NSString *)filename {
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:filename ofType:nil];
// load the data however its formatted
// return the data as an array
return loadedData;
}
- (void)testBMIFormulaInCmAndKgSuccess {
NSArray *testData = [self dataFromFileNamed:@"BMI_data_01.txt"];
for (int i=0; i < testData.count; i++) {
NSArray *dataSet = testData[i];
CGFloat height = dataSet[0];
CGFloat weight = dataSet[1];
CGFloat expectedBMI = dataSet[2];
NSString *actualResult = [self.vcToTest calculateBMIforHeight:height andWeight:weight];
XCTAssertEqual(actualResult, expectedBMI);
}
}
通常最好避免在单元测试代码中使用 for 循环。此规则通常会导致单独的断言。
但在您的情况下,您想使用各种输入来执行一个函数。你的方法一点也不差。我们可以使用文字来简化数组:
NSArray<NSString *> *heightsInMetres = @[ @"193", @"192", @"192", @"165", @"155", @"154"];
NSArray<NSString *> *weightsInKg = @[ @"37", @"37", @"36", @"80", @"120", @"120"];
NSArray<NSString *> *expectedResults = @[@"9.93", @"10.04", @"9.77", @"29.38", @"49.95", @"50.60"];
我通常也避免搞笑格式。但是对齐列有助于我们以类似 table 的格式查看值。
最后,我不会将这些值放在 setUp
中,除非它们用于多个测试。
如果您需要很多这样的测试,可能值得探索使用实际 table 的测试格式,例如 Fitnesse。 Table 数据可以是电子表格或 wiki 格式,驾驶考试。
我正在为现有项目编写单元测试代码。该项目在 Objective-C 中,我必须通过对测试用例的大量输入来测试几个函数。例如,我有一个测试用例来测试输入两个参数的函数计算器。目前我创建数组来将输入值集存储到 运行 测试。使用的代码如下:
- (void)setUp {
[super setUp];
self.vcToTest = [[BodyMassIndexVC alloc] init];
input1 = [[NSMutableArray alloc] initWithObjects:@"193", @"192", @"192", @"165", @"155", @"154", nil];
input2 = [[NSMutableArray alloc] initWithObjects:@"37", @"37", @"36", @"80",@"120", @"120", nil];
}
- (void)testCalculatorSuccess {
for (int i=0; i<input1.count; i++) {
NSArray *expectedResult = [[NSArray alloc] initWithObjects: @"9.93", @"10.04", @"9.77", @"29.38", @"49.95", @"50.60", nil];
NSString *actualResult = [self.vcToTest calculateResult:input1[i] andInput2:input2[i]];
XCTAssertEqualObjects(actualResult, expectedResult[i]);
}
}
我在网上搜索了最佳做法,但没有找到。有人可以帮我弄这个吗?我 运行 是否以正确的方式进行测试?在这种情况下应遵循的最佳做法是什么?我应该为每组输入创建一个测试用例吗?
您的测试 class 应该针对一个特定的事物进行测试,即 sut(被测系统)。在您的情况下,sut 变量应该是您的 vcToTest。我要进行测试的方法是通过依赖注入,而不是将您正在测试的所有数组存储为实例变量。因此,您将创建一个测试方法来接收要测试的参数。这样就不用一直创建实例变量了,只创建和测试方法相关的局部变量。
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
self.sut = [[BodyMassIndexVC alloc] init];
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
self.sut = nil;
[super tearDown];
}
- (void)testBMIFormulaInCmAndKgSuccess {
// local variables that are only seen in this method
NSArray *heightsInMetres = [[NSMutableArray alloc] initWithObjects:@"193", @"192", @"192", @"165", @"155", @"154", nil];
NSArray *weightsInKg = [[NSMutableArray alloc] initWithObjects:@"37", @"37", @"36", @"80",@"120", @"120", nil];
NSArray *expectedResults = [[NSArray alloc] initWithObjects: @"9.93", @"10.04", @"9.77", @"29.38", @"49.95", @"50.60", nil];
for (int i=0; i<heightsInMetres.count; i++) {
[self xTestHeightInMeters:heightsInMetres[i] weightInKg:weightsInKg[i] expecting:expectedResults[i]];
}
}
// the variables that are used to test are injected into this method
- (void)xTestHeightInMeters:(NSString *)height weightInKg:(NSString *)weight expecting:(NSString *)expectedResult {
NSString *result = [self.sut calculateBMIforHeight:height andWeight:weight];
XCTAssertEqual(result, expectedResult);
}
如果我是你,我不会为 运行 测试创建数组。数组很乱,变得很难理解发生了什么,也很容易出错。我会创建特定的测试方法来测试一件事以确保 sut 正常工作。例如,在 TDD 中,您创建一个会失败的测试方法,然后修改您的 sut 以最简单的方式修复此失败。通常这意味着您的修复会 return 完全符合您的预期。然后你做了另一个测试,用不同的值测试完全相同的东西,它现在应该失败,因为你的 sut 只是 returning 他们之前的测试正在寻找的东西。现在您再次修改您的 sut 以使两个测试都通过。在此之后,在大多数情况下您将不需要任何附加测试,因为它已被证明可以以两种独特的方式工作。
我知道您说过您正在测试已经编写好的软件,但我强烈建议您查看测试驱动开发。即使您实际上并不应用 TDD,您也会学习如何创建有意义的测试。 This helped me learn tdd
根据我的经验,关键考虑因素应该是随着时间的推移维护测试套件的难易程度。您当前的方法会在未来以两种方式引起问题:
如果您想使用不同的数字计算 BMI,则需要使用不同的测试 class(因为您在设置方法中锁定了这些值)。
如果您决定使用不同的数学或多个 BMI 方程式,则必须在检查这些值的任何地方更新数组。
我建议改为创建一个包含身高、体重和预期 BMI 值的 CSV 或纯文本文件。这样可以将测试数据保持在一起。然后在您的测试方法中,加载文件并根据预期 BMI 检查您的实际 BMI。
您可以在此处灵活地混合和匹配测试数据,或为不同的 BMI 方程使用不同的测试文件。就个人而言,我也喜欢这样一个事实,即您可以在更改内容时保留旧数据文件,以防您想要回滚或添加旧算法支持。
一个快速而肮脏的版本看起来像这样:
- (NSArray *)dataFromFileNamed:(NSString *)filename {
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:filename ofType:nil];
// load the data however its formatted
// return the data as an array
return loadedData;
}
- (void)testBMIFormulaInCmAndKgSuccess {
NSArray *testData = [self dataFromFileNamed:@"BMI_data_01.txt"];
for (int i=0; i < testData.count; i++) {
NSArray *dataSet = testData[i];
CGFloat height = dataSet[0];
CGFloat weight = dataSet[1];
CGFloat expectedBMI = dataSet[2];
NSString *actualResult = [self.vcToTest calculateBMIforHeight:height andWeight:weight];
XCTAssertEqual(actualResult, expectedBMI);
}
}
通常最好避免在单元测试代码中使用 for 循环。此规则通常会导致单独的断言。
但在您的情况下,您想使用各种输入来执行一个函数。你的方法一点也不差。我们可以使用文字来简化数组:
NSArray<NSString *> *heightsInMetres = @[ @"193", @"192", @"192", @"165", @"155", @"154"];
NSArray<NSString *> *weightsInKg = @[ @"37", @"37", @"36", @"80", @"120", @"120"];
NSArray<NSString *> *expectedResults = @[@"9.93", @"10.04", @"9.77", @"29.38", @"49.95", @"50.60"];
我通常也避免搞笑格式。但是对齐列有助于我们以类似 table 的格式查看值。
最后,我不会将这些值放在 setUp
中,除非它们用于多个测试。
如果您需要很多这样的测试,可能值得探索使用实际 table 的测试格式,例如 Fitnesse。 Table 数据可以是电子表格或 wiki 格式,驾驶考试。