如何为单元与集成测试分别配置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
相关产品推荐
相关产品推荐

