繁体   English   中英

针对不同接口实现的共享 XCTest 单元测试

[英]Shared XCTest unit tests for different implementations of interface

我有一些接口(协议)的两个或多个实现:

protocol Interface {
    func methodOne()
    func methodTwo()
}

我想测试每个实现,我不想重复代码。 我有几个选择,但没有一个能让我满意。

第一个是为ImplementationA创建测试用例并将其子类化以获得ImplementationB的测试用例:

class ImplementationATests: XCTestCase {

    var implToTest: Interface!

    override func setUp() {
        super.setUp()
        implToTest = ImplementationA()
    }

    func testMethodOne() {
        ...
    }

    func testMethodTwo() {
        ...
    }
}


class ImplementationBTests: ImplementationATests {

    override func setUp() {
        super.setUp()
        implToTest = ImplementationB()
    }
}

这种方法的缺点之一是我无法进行仅适用于ImplementationA的测试。 (例如,测试特定于该实现的一些辅助方法)

我想到的第二个选项是为测试用例创建共享子类:

class InterfaceTests: XCTestCase {

    var implToTest: Interface!

    func testMethodOne() {
        ...
    }

    func testMethodTwo() {
        ...
    }
}

但在这里这些测试也将被执行,但它们将失败,因为没有为implToTest分配实现。 当然我可以为它分配一些实现,但是我将以相同实现的两个测试用例结束。 最好的选择是以某种方式禁用InterfaceTests测试用例并仅运行其子类。 是否可以?

我得到的第三个想法可能看起来很棘手,但它会满足我的所有需求。 不幸的是它不起作用。 我决定创建InterfaceTestable协议:

protocol InterfaceTestable {
    var implToTest: Interface! { get set }
}

并使用所有共享测试对其进行扩展:

extension InterfaceTestable {

    func testMethodOne() {
        ...
    }

    func testMethodTwo() {
        ...
    }
}

然后为每个实现创建测试用例:

class ImplementationATests: XCTestCase, InterfaceTestable {

    var implToTest: Interface!

    override func setUp() {
        super.setUp()
        implToTest = ImplementationA()
    }

    // some tests which only apply to ImplementationA
}


class ImplementationBTests: XCTestCase, InterfaceTestable {

    var implToTest: Interface!       

    override func setUp() {
        super.setUp()
        implToTest = ImplementationB()
    }

    // some tests which only apply to ImplementationB
}

这些测试用例编译但 Xcode 没有看到在InterfaceTestable扩展中声明的测试。

有没有其他方法可以为不同的实现共享测试?

我遇到了同样的问题,并使用第二个选项解决了该问题。 但是,我找到了一种方法来阻止基类中的测试用例运行:

覆盖基类中的defaultTestSuite()类方法,以返回空的XCTestSuite

class InterfaceTests: XCTestCase {

  var implToTest: Interface!

  override class func defaultTestSuite() -> XCTestSuite {
    return XCTestSuite(name: "InterfaceTests Excluded")
  }
}

这样就不会运行InterfaceTests测试。 不幸的是,也没有对ImplementationATests测试。 通过覆盖ImplementationATests defaultTestSuite() ,可以解决此问题:

class ImplementationATests : XCTestCase {

  override func setUp() {
    super.setUp()
    implToTest = ImplementationA()
  }

  override class func defaultTestSuite() -> XCTestSuite {
    return XCTestSuite(forTestCaseClass: ImplementationATests.self)
  } 
}

现在, ImplementationATests的测试套件将运行InterfaceTests所有测试,但是如果不设置implToTest ,则不会直接运行InterfaceTests测试。

我之前完成此操作的方式是使用共享基类。 使implToTest nillable。 在基类,如果不提供一个实现,只是return以保护条款进行的检测。

有点烦人的是,该测试运行在不执行任何操作时包含了基类测试的报告。 但这是一个小麻烦。 测试子类将提供有用的反馈。

在 ithron 的解决方案之上构建,如果您仔细设计defaultTestSuite ,则可以消除每个子类重新覆盖它的需要。

class InterfaceTests: XCTestCase {
    override class var defaultTestSuite: XCTestSuite {
        // When subclasses inherit this property, they'll fail this check, and hit the `else`.
        // At which point, they'll inherit the full test suite that generated for them.
        if self == AbstractTest.self {
            return XCTestSuite(name: "Empty suite for AbstractSpec")
        } else {
            return super.defaultTestSuite
        }
    }
}

当然,同样的限制适用:这不会从 Xcode 测试导航器中隐藏空测试套件。

将其概括为AbstractTestCase class

我会 go 更进一步,制作一个基础class AbstractTestCase: XCTestCase来存储这个defaultTestSuite技巧,您的所有其他抽象类都可以从中继承。

为了完整起见,要使其真正抽象,您还需要覆盖所有XCTestCase初始值设定项,以便在尝试实例化您的抽象类时使它们出错。 在 Apple 的平台上,有 3 个初始化器需要覆盖:

  1. -[XCTestCase init]
  2. -[XCTestCase initWithSelector:]
  3. -[XCTestCase initWithInvocation:]

不幸的是,这不能从 Swift 完成,因为最后一个初始化器使用NSInvocation NSInvocation不适用于 Swift(它与 Swift 的 ARC 不兼容)。 所以你需要在目标 C 中实现它。这是我的尝试:

AbstractTestCase.h

#import <XCTest/XCTest.h>

@interface AbstractTestCase : XCTestCase

@end

AbstractTestCase.m

#import "AbstractTestCase.h"

@implementation AbstractTestCase

+ (XCTestSuite *)defaultTestSuite {
    if (self == [AbstractTestCase class]) {
        return [[XCTestSuite alloc] initWithName: @"Empty suite for AbstractTestCase"];
    } else {
        return [super defaultTestSuite];
    }
}

- (instancetype)init {
    self = [super init];
    NSAssert(![self isMemberOfClass:[AbstractTestCase class]], @"Do not instantiate this abstract class!");
    return self;
}

- (instancetype)initWithSelector:(SEL)selector {
    self = [super initWithSelector:selector];
    NSAssert(![self isMemberOfClass:[AbstractTestCase class]], @"Do not instantiate this abstract class!");
    return self;
}

- (instancetype)initWithInvocation:(NSInvocation *)invocation {
    self = [super initWithInvocation:invocation];
    NSAssert(![self isMemberOfClass:[AbstractTestCase class]], @"Do not instantiate this abstract class!");
    return self;
}

@end

用法

然后您可以将其用作抽象测试的超类,例如

class InterfaceTests: AbstractTestCase {
  var implToTest: Interface!

  func testSharedTest() {

  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM