是否可以在没有Xcode的情况下使用XCTest单元测试?

时间:2017-01-03 19:57:04

标签: xcode xctest

我想实施XCTest单元测试,但并不是真的想使用XCode来完成它。如何做到这一点并不明显,我想知道这是否可行?

从目前为止我发现,人们可以得到XCTest完全依赖于XCode的印象。我找到了xcodebuild test命令行支持,但这取决于找到XCode项目或工作区。

我在这里有任何选择吗,或者我只是删掉现有的SenTestingKit代码并恢复到一些家庭酿造单元测试代码?我有一些这样的代码,但它不是正确的事情。

依据/病史:

这不仅仅是我的老朋友。我有一个Objective-C程序,这是我两年前提到过的,为此我根据SenTestingKit开发了一套合理的单元测试。现在我回到这段代码 - 我可能至少要重建这个东西,因为介入库的变化 - 我发现SenTestingKit已经消失,被XCTest取代。好吧....

此代码不是使用XCode开发的,因此没有与之关联的ta .project文件,现在使用SenTestingKit的主程序和Makefile进行测试。 {1}}目标(部分原因是老式的,部分是缺乏对IDE的喜爱,部分原因是这是对Objective-C的实验,所以最初坚持我所知道的。)

3 个答案:

答案 0 :(得分:2)

如果您正在寻找基于Xcode的解决方案,请参阅this及其链接解决方案。

对于完整的基于非Xcode的解决方案,请继续阅读。

几年前我曾经问过类似的答案:Is there any non-Xcode-based command line unit testing tool for Objective-C?但事情发生了变化。

随着时间的推移,XCTest中出现的一个有趣功能是运行自定义测试套件的能力。我曾经成功地为我的研究需求实现它们,这是一个示例代码,它是命令行Mac OS应用程序:

@interface FooTest : XCTestCase
@end

@implementation FooTest
- (void)testFoo {
  XCTAssert(YES);
}
- (void)testFoo2 {
  XCTAssert(NO);
}

@end

@interface TestObserver : NSObject <XCTestObservation>
@property (assign, nonatomic) NSUInteger testsFailed;
@end

@implementation TestObserver

- (instancetype)init {
  self = [super init];

  self.testsFailed = 0;

  return self;
}

- (void)testBundleWillStart:(NSBundle *)testBundle {
  NSLog(@"testBundleWillStart: %@", testBundle);
}

- (void)testBundleDidFinish:(NSBundle *)testBundle {
  NSLog(@"testBundleDidFinish: %@", testBundle);
}

- (void)testSuiteWillStart:(XCTestSuite *)testSuite {
  NSLog(@"testSuiteWillStart: %@", testSuite);
}

- (void)testCaseWillStart:(XCTestCase *)testCase {
  NSLog(@"testCaseWillStart: %@", testCase);
}

- (void)testSuiteDidFinish:(XCTestSuite *)testSuite {
  NSLog(@"testSuiteDidFinish: %@", testSuite);
}

- (void)testSuite:(XCTestSuite *)testSuite didFailWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber {
  NSLog(@"testSuite:didFailWithDescription:inFile:atLine: %@ %@ %@ %tu",
        testSuite, description, filePath, lineNumber);
}

- (void)testCase:(XCTestCase *)testCase didFailWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber {
  NSLog(@"testCase:didFailWithDescription:inFile:atLine: %@ %@ %@ %tu",
        testCase, description, filePath, lineNumber);
  self.testsFailed++;
}

- (void)testCaseDidFinish:(XCTestCase *)testCase {
  NSLog(@"testCaseWillFinish: %@", testCase);
}

@end

int RunXCTests() {
  XCTestObserver *testObserver = [XCTestObserver new];

  XCTestObservationCenter *center = [XCTestObservationCenter sharedTestObservationCenter];
  [center addTestObserver:testObserver];

  XCTestSuite *suite = [XCTestSuite defaultTestSuite];

  [suite runTest];

  NSLog(@"RunXCTests: tests failed: %tu", testObserver.testsFailed);

  if (testObserver.testsFailed > 0) {
    return 1;
  }

  return 0;
}

要编译此类代码,您需要显示XCTest所在文件夹的路径:

# in your Makefile
clang -F/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks XCTestDriver.m

不要指望编译代码,但它应该给你一个想法。如果您有任何疑问,请随时询问。还要按照XCTest框架的标题来了解有关其类及其文档的更多信息。

答案 1 :(得分:2)

谢谢,@ stanislaw-pankevich,给出了一个很好的答案。在这里,为了完整起见,我包括(或多或少)完整的测试程序,其中包括一些额外的细节和评论。

(从我的角度来看,这是一个完整的程序,因为它测试功能 在util.h中定义,此处未包括在内)

档案UtilTest.h

#import <XCTest/XCTest.h>
@interface UtilTest : XCTestCase
@end

档案UtilTest.m

#import "UtilTest.h"
#import "../util.h"  // the definition of the functions being tested

@implementation UtilTest

// We could add methods setUp and tearDown here.
// Every no-arg method which starts test... is included as a test-case.

- (void)testPathCanonicalization
{
    XCTAssertEqualObjects(canonicalisePath("/p1/./p2///p3/..//f3"), @"/p1/p2/f3");
}
@end

驱动程序runtests.m(这是主程序,makefile实际调用它来运行所有测试):

#import "UtilTest.h"
#import <XCTest/XCTestObservationCenter.h>

// Define my Observation object -- I only have to do this in one place
@interface BrownieTestObservation : NSObject<XCTestObservation>
@property (assign, nonatomic) NSUInteger testsFailed;
@property (assign, nonatomic) NSUInteger testsCalled;
@end

@implementation BrownieTestObservation

- (instancetype)init {
    self = [super init];
    self.testsFailed = 0;
    return self;
}

// We can add various other functions here, to be informed about
// various events: see XCTestObservation at
// https://developer.apple.com/reference/xctest?language=objc
- (void)testSuiteWillStart:(XCTestSuite *)testSuite {
    NSLog(@"suite %@...", [testSuite name]);
    self.testsCalled = 0;
}

- (void)testSuiteDidFinish:(XCTestSuite *)testSuite {
    NSLog(@"...suite %@ (%tu tests)", [testSuite name], self.testsCalled);
}

- (void)testCaseWillStart:(XCTestSuite *)testCase {
    NSLog(@"  test case: %@", [testCase name]);
    self.testsCalled++;
}

- (void)testCase:(XCTestCase *)testCase didFailWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber {
    NSLog(@"  FAILED: %@, %@ (%@:%tu)", testCase, description, filePath, lineNumber);
    self.testsFailed++;
}

@end

int main(int argc, char** argv) {
    XCTestObservationCenter *center = [XCTestObservationCenter sharedTestObservationCenter];
    BrownieTestObservation *observer = [BrownieTestObservation new];
    [center addTestObserver:observer];

    Class classes[] = { [UtilTest class], };  // add other classes here
    int nclasses = sizeof(classes)/sizeof(classes[0]);

    for (int i=0; i<nclasses; i++) {
        XCTestSuite *suite = [XCTestSuite testSuiteForTestCaseClass:classes[i]];
        [suite runTest];
    }

    int rval = 0;
    if (observer.testsFailed > 0) {
        NSLog(@"runtests: %tu failures", observer.testsFailed);
        rval = 1;
    }

    return rval;
}

生成文件:

FRAMEWORKS=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks
TESTCASES=UtilTest

%.o: %.m
    clang -F$(FRAMEWORKS) -c $<

check: runtests
    ./runtests 2>runtests.stderr

runtests: runtests.o $(TESTCASES:=.o) ../libmylib.a
    cc -o $@ $< -framework Cocoa -F$(FRAMEWORKS) -rpath $(FRAMEWORKS) \
        -framework XCTest $(TESTCASES:=.o) -L.. -lmylib

注意:

  • 现已弃用XCTestObserver类,并替换为XCTestObservation
  • 测试的结果被发送到共享 XCTestObservationCenter,遗憾的是,它会分散注意到stderr(因此必须重定向到其他地方) - 它似乎无法避免这种情况并且有他们只将 发送到我的观察中心。在我的实际程序中,我用NSLog中的runtests.m调用替换了一个与stdout聊天的函数,因此我可以将其与聊天程序区分为默认的ObservationCenter。
  • 另见overview documentation (假设你正在使用XCode),
  • ...... XCTest API documentation
  • ...以及(例如)/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework/Headers
  • 中文件标题中的注释

答案 2 :(得分:0)

这可以正常工作:

  1. 照常构建您的.xctest目标。默认情况下,它将添加到主机应用程序的插件中,但位置无关。
  2. 使用下面的代码创建一个运行程序命令行工具。 更新:Xcode提供了在/Applications/Xcode.app/Contents/Developer/usr/bin/xctest中相当独立的运行程序 如果您不想创建自己的简单运行器,则可以使用此版本。

  3. 使用测试套件的完整路径运行该工具。

示例跑步者代码:

#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

int main(int argc, const char* argv[]) {
  @autoreleasepool {
    XCTestSuite *suite = [XCTestSuite testSuiteForBundlePath:
      [NSString stringWithUTF8String:argv[1]]];
    [suite runTest];

    // Note that XCTestSuite is very shy in terms of errors,
    // so make sure that it loaded anything indeed:
    if (!suite.testRun.testCaseCount) return 1;

    return suite.testRun.hasSucceeded;
  }
}