如何为iOS TDD存根/模拟C函数?

时间:2013-05-26 15:56:56

标签: ios objective-c c tdd

我目前正在创建一个TDD应用程序,但是我在测试应用程序所需的C函数的使用时遇到了问题。这是我需要测试的示例函数:

UIImageWriteToSavedPhotosAlbum(imageToBeSaved, nil, nil, nil)

如何为TDD模拟或存根C方法?

3 个答案:

答案 0 :(得分:4)

有几种方法可以做到但不如OOP语言那么漂亮。这是我使用的列表:

使用函数指针

这种方法是我最喜欢的方法,因为在我看来这是最灵活的。

在头文件(* .h)

int (*_fun) (int);

int fun (int a);

int fun_mock (int a);

在测试用例文件(* .C)

_fun = fun_mock;

在正常情况文件(* .C)

_fun = fun;

调用函数(main.C)

...
_fun ();
...

编译

如果要制作TDD,则需要编译测试文件和主文件(或其余文件)。否则不要编译测试文件。

使用宏替换函数名称

在标题文件(* .h)

如果你想打个招呼

#define FUN fun

int fun (int a);

int fun_mock (int a);

在标题文件(* .h)

如果你想调用模拟版

#define FUN fun_mock

int fun (int a);

int fun_mock (int a);

调用函数(main.C)

...
FUN ();
...

使用此方法,我们需要在编译任何模块之前设置正确的定义。

使用带有函数指针的结构

说实话,我从未使用过这种方法,但我在其他地方读过。主要思想是为模块的所有不同函数提供结构指针,每当您想要更改函数时,只需更改该结构上此函数指针的地址即可。最后是与第一种方法类似的策略,但以不同的方式实现。

根据Timothy Jones的说法

test-dept是一个相对较新的C单元测试框架,允许您对函数进行运行时存根。我发现它很容易使用 - 这是他们的文档中的一个例子:

void test_stringify_cannot_malloc_returns_sane_result() {
 replace_function(&malloc, &always_failing_malloc);
 char *h = stringify('h');
 assert_string_equals("cannot_stringify", h);
}

虽然下载部分有点过时,但似乎相当积极地开发 - 作者修复了一个我非常及时的问题。你可以通过以下方式获得最新版本(我一直在使用,没有问题):

svn checkout http://test-dept.googlecode.com/svn/trunk/ test-dept-read-only 该版本于2011年10月更新。

但是,由于使用汇编程序实现了存根,因此可能需要一些努力才能使其支持ARM。

我从https://stackoverflow.com/a/9613821/2420872复制的最后一点。

答案 1 :(得分:2)

我喜欢在Objective C方法中包装C函数,以便可以使用Objective C模拟框架(例如OCMock)对其进行存根/模拟/预期。

<强> SomeClass.m:

@implementation SomeClass
#pragma mark - Public
// This is the method we will test. It calls a C function internally, which we want to stub.
- (void)someMethod {
    UIImage *image = [self getImage];
    [self writeImageToSavedPhotosAlbum:image
                      completionTarget:nil
                    completionSelector:nil
                           contextInfo:NULL];
}

#pragma mark - Private
- (void)writeImageToSavedPhotosAlbum:(UIImage *)image
                    completionTarget:(id)completionTarget
                  completionSelector:(SEL)completionSelector
                         contextInfo:(void *)contextInfo
{
    UIImageWriteToSavedPhotosAlbum(image, completionTarget, completionSelector, contextInfo);
}
@end

<强> SomeClassTests.m:

@interface SomeClass ()
- (void)writeImageToSavedPhotosAlbum:(UIImage *)image
                    completionTarget:(id)completionTarget
                  completionSelector:(SEL)completionSelector
                         contextInfo:(void *)contextInfo;
@end

@interface SomeClassTests : XCTestCase
@end

@implementation SomeClassTests
- (void)testSomeMethod {
    SomeClass *someInstance = [[SomeClass alloc] init];
    id someInstanceMock = [OCMockObject partialMockForObject:someInstance];
    [[someInstanceMock stub] writeImageToSavedPhotosAlbum:[OCMArg any]
                                         completionTarget:nil
                                       completionSelector:nil
                                              contextInfo:NULL];

    [someInstance someMethod];
}
@end

答案 2 :(得分:2)

目标C解决方案

定义一个返回C函数指针的类方法:

+ (void (*)(UIImage *, id, SEL, void *))writeToSavedPhotosAlbumFunction {
    return &UIImageWriteToSavedPhotosAlbum;
}

在生产代码中使用此类方法返回的C函数:

[SomeClass writeToSavedPhotosAlbumFunction](myImage, myTarget, mySelector, myContextInfo);

定义一个伪C函数来替换我们依赖的C函数:

static void FakeWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo) {
    // Verify arguments.
    // If this function has a return type, return a value.
    // To verify that this function was called, write to a global variable
    // then have the test case check the value of that global variable.
}

将类方法存根以在测试代码中返回指向伪C函数的指针(此示例使用Kiwi):

[SomeClass stub:@selector(writeToSavedPhotosAlbumFunction) andReturn:theValue(&FakeWriteToSavedPhotosAlbum)];

我希望这会有所帮助:)