Foundry是一个面向Objective-C开发者的库,可以极大地减轻单元测试中的许多痛苦。在单元测试期间的最大问题之一是制造适当的测试/模拟模型对象。想象一下,您有一个想要测试的“Person”(人)对象。人有一些属性,如姓名、电子邮件地址、密码等,因此您需要找到一种创建这些测试对象的方法。
这是最简单的方法,只需创建对象并分配静态值,如下所示。
Person *testPerson = [Person new];
testPerson.name = @"Randy Marsh";
testPerson.email = @"[email protected]";
testPerson.password = @"secret";
这很简单。问题是它创造了非常窄的道路,并不能真正创建检查您模型耐用性的好测试。让我们来看一下这里的一些问题:
所以这种方法对小事来说很好,但很快就无法管理。那还有别的吗?
这种方法很糟糕,但我们都在某个时候使用过。它的过程是这样的:首先,我们定义一个可以调用以生成特定长度的随机字符串的实用方法,然后我们做这样的事情:
Person *testPerson = [Person new];
testPerson.name = randomStringWithCharacterCount(10);
testPerson.email = [NSString stringWithFormat@"%@@%@.com", randomStringWithCharacterCount(6), randomStringWithCharacterCount(6)];
testPerson.password = randomStringWithCharacterCount(arc4_random_uniform(8) + 6);
此方法允许我们创建一个“动态”对象,然后可以多次产生并得到不同的值。但它仍然存在许多问题:
我们可以做得更好,对吧?
这是一个很好的方法,它涉及到使用一个伪造数据库(例如Gizou,它是Foundry的构建基础)来填充模型属性。由于数据是从一大组预打包的合法名称、前缀、后缀等中组装的,因此提供了非常真实的数据。要使用它,你可能需要这样做。
Person *testPerson = [Person new];
testPerson.name = [GZNames name];
testPerson.email = [GZInternet email];
testPerson.password = [GZWords word];
这样好多了,现在我们有一些不错的动态对象,拥有真实的数据。但仍有几个缺点
我们真正需要的是类似于factory_girl的东西,但针对Objective-C...
进来Foundry。它通过提供使用“真实”数据轻松铸造模型对象的方法来解决所有这些问题,并特别处理Core Data实体和运行时属性设定。我们开始使用Foundry(使用Cocoapods安装并导入“Foundry.h”头文件)所有要做的就是拿我们的Person模型并采用TGFoundryObject协议。
@interface Person : NSObject <TGFoundryObject>
...
然后只需创建一个构建规范,通过实现单个必需方法foundryBuildSpecs
来完成对对象的构建。构建规范是一个字典,告诉Foundry如何构建你的模型,如下所示
+ (NSDictionary *)foundryBuildSpecs
{
return @{
@"name": [NSNumber numberWithInteger:FoundryPropertyTypeName],
@"email": [NSNumber numberWithInteger:FoundryPropertyTypeEmail],
@"password": [NSNumber numberWithInteger:FoundryPropertyTypeLoremIpsumShort]
};
}
尽管NSNumber包装器很烦人,但这仍然非常简单。你只需创建你想要的规范,然后Foundry就可以构建你的对象了。
// Let's build a single person first.
Person *fullPerson = [Person foundryBuild];
// Not enough, let's make 10 people!
NSArray *bunchOfPeople = [Person foundryBuildNumber:10];
很酷,对吧?如果你只想有一个有效属性的散列,而不是完整的对象呢?
// Let's get a dictionary of attributes for a single person.
NSDictionary *personDictionary = [Person foundryAttributes];
// Now how about 10 people?
NSArray *lotsOfPeople = [Person foundryAttributesNumber:10];
这很简单。但是如果你使用Core Data怎么办?Foundry已经为你考虑到了。
// Let's first build a person without saving them.
Person *newPerson = [Person foundryBuildWithContext:context];
// How about building AND saving a person?
Person *anotherPerson = [Person foundryCreateWithContext:context];
// How about creating 10 saved people?
NSArray *tenPeople = [Person foundryCreateNumber:10 withContext:context];
如果你在构建过程中想运行时手动分配值怎么办?很简单,你只需在构建规范中将类型设置为FoundryPropertyTypeCustom
,然后实现协议方法。
(id)foundryAttributeForProperty:(NSString *)property
{
if ([property isEqualToString:@"name"]) {
// Add your custom assignment code here...
}
}
目前你可以通过自定义属性类型来设置这些内容,但不久Foundry将添加嵌套工厂的选项,使你可以将工厂分配给关系属性作为构建规范的一部分。敬请期待!
Foundry是为OSX 10.7及更高版本和iOS 6.0及更高版本设计的。最好的办法是使用Cocoapods安装。
如果你想运行测试套件,请克隆回购库,在主目录中运行“pod install”,然后执行“rake spec”。
所有类均使用appledoc完全文档化,您可以从存档中使用“rake docs:generate”生成文档,或者可以直接从Cocoadocs中获取它们。
John Tumminaro, [email protected]
Foundry 在 MIT 许可证下可用。更多信息请参阅 LICENSE 文件。