这是一个简单的库,用于帮助定义用于测试iOS/Mac应用程序的模型工厂。大量基于Ruby的FactoryGirl。
我们对类SomeClass
进行测试,需要一个Data Value Object User
的实例。该类只使用username
属性。
在测试中,我们必须生成一个有效的User对象,尽管我们几乎不关心它的所有属性。
SpecBegin(SomeClass)
__block SomeClass *subject;
before(^{
User *user = [[User alloc] init];
user.firstName = @"Bill";
user.lastName = @"Smith";
user.friendCount = 7;
user.title = @"Mr";
user.maidenName = @"Bloggs";
subject = [[SomeClass alloc] initWithUser:user];
});
it(@"is valid", ^{
expect([subject isValid]).to.beTruthy();
});
});
这种设置代码开始主导甚至是最简单的测试,随着我们开始编写更多需要User
的测试,我们最终编写了ModelFixtures
类来简化代码。
SpecBegin(SomeClass)
__block SomeClass *subject;
before(^{
User *user = [ModelFixtures user];
subject = [[SomeClass alloc] initWithUser:user];
});
it(@"is valid", ^{
expect([subject isValid]).to.beTruthy();
});
});
这可以暂时解决,但后来我们意识到我们需要为不同的测试创建多个略有不同的测试用例。因此,ModelFixtures
类到处都是附加方法来帮助完成这项工作
@interface ModelFixtures : NSObject
+ (User *)user;
+ (User *)userWithFirstName:(NSString *)firstName;
+ (User *)userWithFirstName:(NSString *)firstName
lastName:(NSString *)lastName;
+ (User *)userWithFirstName:(NSString *)firstName
lastName:(NSString *)lastName
maidenName:(NSString *)maidenName;
@end
使用FactoryGentleman,您在一个文件中定义对象的基字段,然后构建对象,同时也可以覆盖所需的任何字段。
创建一个包含工厂定义的实现文件 (*.m)
#import <FactoryGentleman/FactoryGentleman.h>
#import "User.h"
FGFactoryBegin(User)
builder[@"firstName"] = @"Bob";
builder[@"lastName"] = @"Bradley";
[builder field:@"friendCount" integerValue:10];
builder[@"title"] = @"Mr";
builder[@"maidenName"] = @"Macallister";
FGFactoryEnd
#import <FactoryGentleman/FactoryGentleman.h>
#import "User.h"
SpecBegin(User)
__block User *subject;
before(^{
subject = FGBuild(User.class);
});
it(@"is valid", ^{
expect([subject isValid]).to.beTruthy();
});
});
构建对象时,您可以传递构建器块或传递值字典来覆盖字段
#import <FactoryGentleman/FactoryGentleman.h>
#import "User.h"
SpecBegin(User)
__block User *subject;
context(@"when user has no first name", ^{
before(^{
subject = FGBuildWith(User.class, ^(FGDefinitionBuilder *builder) {
[builder nilField:@"firstName"];
});
});
it(@"is NOT valid", ^{
expect([subject isValid]).to.beFalsy();
});
});
});
或通过传递一个值字典
#import <FactoryGentleman/FactoryGentleman.h>
#import "User.h"
SpecBegin(User)
__block User *subject;
context(@"when user has no first name", ^{
before(^{
subject = FGBuildWith(User.class, @{ @"firstName" : @"" });
});
it(@"is NOT valid", ^{
expect([subject isValid]).to.beFalsy();
});
});
});
您可以使用块定义具有一些更复杂状态相关值的字段
#import <FactoryGentleman/FactoryGentleman.h>
#import "User.h"
FGFactoryBegin(User)
__block int currentId = 0;
[builder field:@"resourceId" by:^{
return @(++currentId);
}];
builder[@"firstName"] = @"Bob";
builder[@"lastName"] = @"Bradley";
[builder field:@"friendCount" integerValue:10];
builder[@"title"] = @"Mr";
builder[@"maidenName"] = @"Macallister";
FGFactoryEnd
您可以通过列出初始化器选择符和所需的字段名称来定义具有不可变(即只读)属性的对象
#import <FactoryGentleman/FactoryGentleman.h>
#import "User.h"
FGFactoryBegin(User)
builder[@"firstName"] = @"Bob";
builder[@"lastName"] = @"Bradley";
[builder field:@"friendCount" integerValue:10];
builder[@"title"] = @"Mr";
builder[@"maidenName"] = @"Macallister";
[builder initWith:@selector(initWithFirstName:lastName:) fieldNames:@[ @"firstName", @"lastName" ]];
FGFactoryEnd
您可以通过提供所需的工厂名称来定义关联对象(具有工厂定义的对象)
#import <FactoryGentleman/FactoryGentleman.h>
#import "User.h"
FGFactoryBegin(User)
builder[@"firstName"] = @"Bob";
builder[@"lastName"] = @"Bradley";
builder[@"friendCount"] = @10;
builder[@"title"] = @"Mr";
builder[@"maidenName"] = @"Macallister";
[builder field:@"address" assoc:Address.class];
FGFactoryEnd
对于具有不同属性的对象,您可以在基本定义中定义它们
#import <FactoryGentleman/FactoryGentleman.h>
#import "User.h"
FGFactoryBegin(User)
builder[@"firstName"] = @"Bob";
builder[@"lastName"] = @"Bradley";
builder[@"friendCount"] = @10;
builder[@"title"] = @"Mr";
builder[@"maidenName"] = @"Macallister";
[builder field:@"address" assoc:Address.class];
traitDefiners[@"homeless"] = ^(FGDefinitionBuilder *homelessBuilder) {
[homelessBuilder nilField:@"address"];
};
FGFactoryEnd
然后可以使用相应的构建宏来使用这些属性
subject = FGBuildTrait(User.class, @"homeless");
subject = FGBuildTraitWith(User.class, @"homeless", ^(FGDefinitionBuilder *builder) {
builder[@"firstName"] = @"Brian";
});
以及关联定义
[builder field:@"user" assoc:User.class trait:@"homeless"];
您可以定义只读属性(非原始)的值,尽管这个功能默认不可用。为了设置它们,您必须在您的头文件中定义FG_ALLOW_READONLY
。
将pod "FactoryGentleman"
添加到您的Podfile
FactoryGentleman在MIT许可证下提供。有关更多信息,请参阅LICENSE文件。