You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为单元与集成测试分别配置App启动时的不同行为?

测试App启动阶段TabBarController的标签配置(基于FeatureFlag依赖注入)

问题背景

需要验证App启动时TabBarController的标签数量与类型——这些行为由A/B服务的isNewFeatureEnabled配置决定,且仅在启动阶段读取该配置值。根据配置不同,TabBar会加载不同的视图控制器组合:

  • 启用新功能时,TabBar包含3个标签
  • 禁用新功能时,TabBar包含5个标签

现有代码实现

TabBar配置逻辑

var viewControllers: [UIViewController] = [ tabOne, tabTwo]
if Config.isNewFeatureEnabled {
    viewControllers.append(self._menuCoordinator.rootViewController)
} else {
    viewControllers.append(self._anotherTabBarController)
    viewControllers.append(self._anotherCoordinator.rootViewController)
    viewControllers.append(self._someOtherCoordinator.rootViewController)
}
_tabBarController.viewControllers = viewControllers

FeatureFlag协议与实现

protocol FeatureFlag {
    var isNewFeatureEnabled: Bool { get set }
}

// 生产环境实现
class FeatureFlagService: FeatureFlag {
   var isNewFeatureEnabled = false
   // 其他FeatureFlag配置项
}

待完善的测试用例

class NewFeatureVisibilityTests: XCTestCase {
    func test_TabBar_has_threeTabs_when_NewFeature_isEnabled() {
        // 需要注入启用新功能的配置

        guard let tabBar = appDel.currentWindow?.rootViewController as? UITabBarController else {
            return XCTFail("根视图控制器应为UITabBarController")
        }

        XCTAssertEqual(tabBar.viewControllers?.count, 3)
    }

    func test_TabBar_has_fiveTabs_when_NewFeature_isDisabled() {
        // 需要注入禁用新功能的配置

        guard let tabBar = appDel.currentWindow?.rootViewController as? UITabBarController else {
            return XCTFail("根视图控制器应为UITabBarController")
        }

        XCTAssertEqual(tabBar.viewControllers?.count, 5)
    }
}

解决方案:通过依赖注入实现测试隔离

1. 重构启动逻辑,依赖FeatureFlag协议

将直接依赖Config.isNewFeatureEnabled的代码改为依赖FeatureFlag协议,通过构造函数注入配置实例,确保启动逻辑可测试:

class AppCoordinator {
    let tabBarController = UITabBarController()
    private let featureFlag: FeatureFlag
    
    // 构造函数注入FeatureFlag
    init(featureFlag: FeatureFlag) {
        self.featureFlag = featureFlag
    }
    
    func configureTabBar() {
        var viewControllers: [UIViewController] = [tabOne, tabTwo]
        if featureFlag.isNewFeatureEnabled {
            viewControllers.append(self._menuCoordinator.rootViewController)
        } else {
            viewControllers.append(self._anotherTabBarController)
            viewControllers.append(self._anotherCoordinator.rootViewController)
            viewControllers.append(self._someOtherCoordinator.rootViewController)
        }
        tabBarController.viewControllers = viewControllers
    }
}

2. 修改AppDelegate支持测试注入

调整AppDelegate,允许在测试时注入自定义的FeatureFlag实例,避免依赖全局配置:

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    private var appCoordinator: AppCoordinator!
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 生产环境使用真实FeatureFlag服务
        let featureFlag = FeatureFlagService()
        // 这里添加从A/B服务拉取配置的逻辑
        
        setupCoordinator(with: featureFlag)
        return true
    }
    
    // 为测试暴露的初始化方法
    init(featureFlag: FeatureFlag) {
        super.init()
        setupCoordinator(with: featureFlag)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupCoordinator(with featureFlag: FeatureFlag) {
        appCoordinator = AppCoordinator(featureFlag: featureFlag)
        appCoordinator.configureTabBar()
        
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = appCoordinator.tabBarController
        window?.makeKeyAndVisible()
    }
}

3. 完善测试用例,实现隔离测试

在每个测试方法中创建独立的FeatureFlag实例,控制配置状态,确保测试用例互不影响:

class NewFeatureVisibilityTests: XCTestCase {
    func test_TabBar_has_threeTabs_when_NewFeature_isEnabled() {
        // 创建启用新功能的测试配置
        let testFeatureFlag = FeatureFlagService()
        testFeatureFlag.isNewFeatureEnabled = true
        
        // 初始化AppDelegate并传入测试配置
        _ = AppDelegate(featureFlag: testFeatureFlag)
        
        guard let tabBar = UIApplication.shared.keyWindow?.rootViewController as? UITabBarController else {
            return XCTFail("根视图控制器应为UITabBarController")
        }
        
        XCTAssertEqual(tabBar.viewControllers?.count, 3)
    }
    
    func test_TabBar_has_fiveTabs_when_NewFeature_isDisabled() {
        // 创建禁用新功能的测试配置
        let testFeatureFlag = FeatureFlagService()
        testFeatureFlag.isNewFeatureEnabled = false
        
        // 初始化AppDelegate并传入测试配置
        _ = AppDelegate(featureFlag: testFeatureFlag)
        
        guard let tabBar = UIApplication.shared.keyWindow?.rootViewController as? UITabBarController else {
            return XCTFail("根视图控制器应为UITabBarController")
        }
        
        XCTAssertEqual(tabBar.viewControllers?.count, 5)
    }
}

说明

通过依赖注入,每个测试用例可以独立控制FeatureFlag的状态,避免了全局配置带来的测试污染,保证了测试的准确性与隔离性。

内容的提问来源于stack exchange,提问作者nesimtunc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 21:31:02