【问题标题】:Test for NSLog output测试 NSLog 输出
【发布时间】:2016-05-15 09:21:35
【问题描述】:

我正在编写一个框架,其中还包括一个可以在运行时启用/禁用的简单记录器。由于我希望对尽可能多的框架进行单元测试,因此我还想测试记录器是否正常工作。

记录器只是通过NSLog 记录。现在我需要测试输出是否真的符合预期(即它是否真的记录并且是正确格式的输出)。我找不到使用 Xcode 的 XCTest 框架的方法。

我可以修改记录器,使其在测试时不使用NSLog,但这让我觉得很容易出错。那么,有没有更好的方法来检查NSLog 的输出?

【问题讨论】:

  • 我是 TDD 新手,所以你能解释一下在你的项目中测试系统 API 而不是模拟 NSLog 并围绕它测试代码的原因
  • 作为答案,您可以检查这个问题stackoverflow.com/q/9619708/1403732,其中asker 告诉他他将NSLog 流重定向到文件。我认为这对您来说是一个很好的起点
  • 我确实想测试NSLog 是否有效,我想测试日志记录是否正确完成,直到最后。如果可能,这涉及检查NSLog 的输出。重定向可能确实是一种调查方式。它可能会隐藏测试环境本身的大部分/所有输出,所以我必须重定向只是为了测试,然后恢复重定向……这是我应该玩的东西。之前考虑过,但错过了测试后我可能可以恢复重定向。
  • @sage444:所以我确实通过重定向文件描述符来解决它。谢谢你让我再次思考这个问题:-)

标签: xcode xctest


【解决方案1】:

有记录器输出字符串。让另一个对象打印这些。这样您就可以将第一个对象与第二个对象分开进行单元测试。

【讨论】:

  • 有趣的想法。虽然这可以让我测试输出是否具有所需的内容,但我仍然无法测试输出是否实际发生。
  • 那么问题是:你不信任NSLog吗?您想为系统框架中的功能编写单元吗?
  • 实际上,这与 NSLog 无关。它是关于测试是否执行了完整的操作(记录消息),直到最后。到目前为止,我提出的所有想法(也包括您的建议)只允许我测试部分代码:总会有一些我目前无法测试的代码,因此可能会在没有人注意到的情况下意外损坏.不过,我想出了一种设计,可以将不可测试的代码减少到只有一条微不足道的行。所以这对我来说已经足够了,但我更喜欢 100% 的解决方案。
  • 我明白了。编写单元测试以确保 NSLog 实际写入控制台的唯一方法是检查控制台。不确定如何/是否可以这样做。
【解决方案2】:

我通过编写一个将文件描述符重定向到文件并能够重置此重定向的实用程序类解决了这个问题:

@interface IORedirector : NSObject

- (instancetype)initWithFileDescriptor:(int)fileDescriptor targetPath:(NSURL *)fileURL flags:(int)oflags mode:(mode_t)mode;

- (void)reset;

@end

@implementation IORedirector
{
    int _fileDescriptor;
    int _savedFileDescriptor;
}

- (instancetype)initWithFileDescriptor:(int)fileDescriptor targetPath:(NSURL *)fileURL flags:(int)oflags mode:(mode_t)mode
{
    _savedFileDescriptor = dup(fileDescriptor);
    if (_savedFileDescriptor == -1) {
        NSLog(@"Could not save file descriptor %d: %s", fileDescriptor, strerror(errno));
        return nil;
    }

    int tempFD = open(fileURL.path.fileSystemRepresentation, oflags, mode);
    if (tempFD == -1) {
        NSLog(@"Could not open %@: %s", fileURL.path, strerror(errno));
        close(_savedFileDescriptor);
        return nil;
    }

    if (close(fileDescriptor) == -1) {
        NSLog(@"Closing file descriptor %d failed: %s", fileDescriptor, strerror(errno));
        close(_savedFileDescriptor);
        close(tempFD);
        return nil;
    }

    if (dup2(tempFD, fileDescriptor) == -1) {
        NSLog(@"Could not replace file descriptor %d with new one for %@: %s", fileDescriptor, fileURL.path, strerror(errno));
        close(_savedFileDescriptor);
        close(tempFD);
        return nil;
    }

    _fileDescriptor = fileDescriptor;
    close(tempFD);

    return self;
}

- (void)dealloc
{
    [self reset];
}

- (void)reset
{
    if (_savedFileDescriptor == -1) return;

    if (close(_fileDescriptor) == -1) {
        NSLog(@"Closing file descriptor %d failed: %s", _fileDescriptor, strerror(errno));
        return;
    }

    if (dup2(_savedFileDescriptor, _fileDescriptor) == -1) {
        NSLog(@"Could not restore file descriptor %d: %s", _fileDescriptor, strerror(errno));
        return;
    }

    close(_savedFileDescriptor);
    _savedFileDescriptor = -1;
}

@end

初始化程序要么返回一个有效实例(重定向已建立),要么返回nil。立即致电reset 恢复重定向。

初始化程序和reset 都可能失败并“丢失”原始文件描述符,但窗口很小且不太可能。这可能仅在多线程应用程序中使用此类时才相关(我不这样做),因此如果您打算在多线程环境中使用它,您需要确保一次只能运行一个初始化程序或reset使用全局互斥锁之类的。

使用这个类(当然,我有一个单独的测试用例),我可以将STDERR_FILENO 重定向到一个文件,从而捕获NSLog 的输出。这样我就可以测试未修改的日志类了。

为临时文件创建合适的路径留给读者作为练习。

【讨论】:

    猜你喜欢
    • 2015-01-02
    • 2018-11-17
    • 2016-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    相关资源
    最近更新 更多