Làm thế nào để cho ứng dụng biết nếu nó đang chạy Unit tests trong một dự án Swift thuần túy?


81

Một điều khó chịu khi chạy thử nghiệm trong Xcode 6.1 là toàn bộ ứng dụng phải chạy và khởi chạy bảng phân cảnh và bộ điều khiển chế độ xem gốc của nó. Trong ứng dụng của tôi, điều này chạy một số lệnh gọi máy chủ tìm nạp dữ liệu API. Tuy nhiên, tôi không muốn ứng dụng làm điều này khi chạy thử nghiệm.

Với các macro tiền xử lý đã biến mất, điều gì tốt nhất để dự án của tôi biết rằng nó đã được khởi chạy khi chạy thử nghiệm chứ không phải là một lần khởi chạy bình thường? Tôi chạy chúng bình thường với command+ Uvà trên bot.

Mã giả:

// Appdelegate.swift
if runningTests() {
   return
} else {
   // do ordinary api calls
}

"toàn bộ ứng dụng phải chạy và khởi chạy bảng phân cảnh và bộ điều khiển chế độ xem gốc" có đúng không? Tôi đã không thử nghiệm nó nhưng nó có vẻ không phù hợp với tôi. Hmm ...
Fogmeister

Vâng, các ứng dụng đã kết thúc phóng được điều hành cũng như viewDidLoad cho bộ điều khiển xem gốc
Bogen

Ah, vừa mới thử nghiệm. Không nghĩ rằng đó là trường hợp lol. Điều này là gì về điều này đang gây ra vấn đề trong các bài kiểm tra của bạn? Có lẽ có một cách khác xung quanh nó?
Fogmeister

Tôi chỉ cần thông báo cho ứng dụng biết quá trình chạy của nó với các bài kiểm tra, vì vậy một cờ như macro bộ xử lý cũ sẽ hoạt động, nhưng chúng không được hỗ trợ nhanh chóng.
bogen

Vâng, nhưng tại sao bạn "cần" làm điều đó? Điều gì đang khiến bạn nghĩ rằng bạn cần phải làm điều đó?
Fogmeister

Câu trả lời:


44

Thay vì kiểm tra xem các bài kiểm tra có đang chạy để tránh các tác dụng phụ hay không, bạn có thể chạy các bài kiểm tra mà không cần chính ứng dụng lưu trữ. Đi tới Cài đặt Dự án -> chọn mục tiêu kiểm tra -> Chung -> Kiểm tra -> Ứng dụng Máy chủ -> chọn 'Không có'. Chỉ cần nhớ bao gồm tất cả các tệp bạn cần để chạy thử nghiệm, cũng như các thư viện thường được bao gồm bởi mục tiêu ứng dụng Máy chủ.

nhập mô tả hình ảnh ở đây


1
Làm thế nào để bao gồm các tiêu đề bắc cầu cho đích sau khi xóa ứng dụng máy chủ lưu trữ?
Bhargav

Đã thử câu này, nó đã phá vỡ các bài kiểm tra của tôi cho đến khi tôi nhận được câu trả lời này đề xuất làm ngược lại: stackoverflow.com/a/30939244/1602270
Eagle.dan.1349 Ngày

nếu tôi làm điều đó, tôi có thể kiểm tra cloudKit (ví dụ), vì vậy giải pháp cho tôi là phát hiện trong applicationDidFinishLaunching nếu tôi đang kiểm tra và nếu CÓ, sau đó quay lại mà không cần phân bổ các lớp chính của ứng dụng.
user1105951

83

Câu trả lời của Elvind không tệ nếu bạn muốn có thứ từng được gọi là "Phép thử logic" thuần túy. Nếu bạn vẫn muốn chạy ứng dụng máy chủ chứa của mình nhưng mã thực thi có điều kiện hoặc không thực thi tùy thuộc vào việc thử nghiệm có được chạy hay không, bạn có thể sử dụng cách sau để phát hiện xem gói thử nghiệm đã được đưa vào hay chưa:

if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
     // Code only executes when tests are running
}

Tôi đã sử dụng cờ biên dịch có điều kiện như được mô tả trong câu trả lời này để chi phí thời gian chạy chỉ phát sinh trong các bản dựng gỡ lỗi:

#if DEBUG
    if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
        // Code only executes when tests are running
    }
#endif

Chỉnh sửa Swift 3.0

if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
    // Code only executes when tests are running
}

3
Không làm việc trong Xcode mới nhất - tên biến môi trường dường như đã thay đổi
amleszk

7
Không chắc chính xác khi nào điều này ngừng hoạt động, nhưng với Xcode 7.3, tôi hiện đang sử dụng XCTestConfigurationFilePathkhóa môi trường thay vì XCInjectBundle.
ospr

Cảm ơn @ospr, tôi đã chỉnh sửa câu trả lời cho công việc với Xcode 7.3
Michael McGuire

@tkuichooseyou po ProcessInfo.processInfo.environmentkhông có khóa XCTestConfigurationFilePath. Bạn có thể vui lòng chia sẻ mã của bạn? Đây được kiểm tra agains một mục tiêu UITest
Tal Zion

@TalZion xin lỗi, không nhận ra rằng bạn có ý định cho một mục tiêu UITest. Bạn đã thử NSClassFromString("XCTest")phương pháp dưới đây?
tkuichooseyou

44

Tôi sử dụng cái này trong ứng dụng: didFinishLaunchingWithOptions:

// Return if this is a unit test
if let _ = NSClassFromString("XCTest") {
    return true
}

3
Điều này chỉ áp dụng cho Bài kiểm tra đơn vị chứ không phải Kiểm tra giao diện người dùng Xcode 7 mới.
Jesse

32

Khác, theo ý kiến ​​của tôi cách đơn giản hơn:

Bạn chỉnh sửa lược đồ của mình để chuyển một giá trị boolean làm đối số khởi chạy cho ứng dụng của mình. Như thế này:

Đặt đối số khởi chạy trong Xcode

Tất cả các đối số khởi chạy được tự động thêm vào của bạn NSUserDefaults.

Bây giờ bạn có thể tải BOOL như:

BOOL test = [[NSUserDefaults standardUserDefaults] boolForKey:@"isTest"];

2
Có vẻ như đây là cách sạch sẽ nhất mà tôi tìm thấy cho đến nay. Chúng tôi không phải thêm tất cả các tệp ứng dụng vào mục tiêu thử nghiệm và chúng tôi không phải dựa vào một số giải pháp kỳ lạ để kiểm tra "XCTestConfigurationFilePath"hoặc NSClassFromString("XCTest"). Tôi đã thực hiện giải pháp này trong trong Swift với một chức năng toàn cầufunc isRunningTests() -> Bool { return UserDefaults.standard.bool(forKey: "isRunningTests") }
Kevin Hirsch

21

Tôi tin rằng hoàn toàn chính đáng khi muốn biết liệu bạn có đang chạy trong một bài kiểm tra hay không. Có rất nhiều lý do tại sao điều đó có thể hữu ích. Ví dụ: trong các thử nghiệm đang chạy, tôi quay lại sớm từ các phương thức khởi chạy ứng dụng đã làm / sẽ-kết thúc trong App Delegate, giúp các thử nghiệm bắt đầu nhanh hơn đối với mã không phải là bài kiểm tra đơn vị của tôi. Tuy nhiên, tôi không thể thực hiện bài kiểm tra "logic" thuần túy, vì nhiều lý do khác.

Tôi đã từng sử dụng kỹ thuật tuyệt vời được mô tả bởi @Michael McGuire ở trên. Tuy nhiên, tôi nhận thấy rằng nó đã ngừng hoạt động đối với tôi xung quanh Xcode 6.4 / iOS8.4.1 (có lẽ nó đã bị hỏng sớm hơn).

Cụ thể là tôi không thấy XCInjectBundle nữa khi chạy thử nghiệm bên trong mục tiêu thử nghiệm cho một khuôn khổ của tôi. Đó là, tôi đang chạy bên trong một mục tiêu thử nghiệm để kiểm tra một khuôn khổ.

Vì vậy, bằng cách sử dụng phương pháp @Fogmeister đề xuất, mỗi lược đồ thử nghiệm của tôi hiện đặt một biến môi trường mà tôi có thể kiểm tra.

nhập mô tả hình ảnh ở đây

Sau đó, đây là một số mã tôi có trên một lớp được gọi là APPSTargetConfigurationcó thể trả lời câu hỏi đơn giản này cho tôi.

static NSNumber *__isRunningTests;

+ (BOOL)isRunningTests;
{
    if (!__isRunningTests) {
        NSDictionary *environment = [[NSProcessInfo processInfo] environment];
        NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
        __isRunningTests = @([isRunningTestsValue isEqualToString:@"YES"]);
    }

    return [__isRunningTests boolValue];
}

Một lưu ý với cách tiếp cận này là nếu bạn chạy thử nghiệm từ lược đồ ứng dụng chính của mình, như XCTest sẽ cho phép bạn làm, (nghĩa là không chọn một trong các lược đồ thử nghiệm của bạn), bạn sẽ không nhận được bộ biến môi trường này.


5
thay vì thêm nó trong 'Run', sẽ hữu ích hơn nếu chúng ta thêm nó vào 'Test' cho tất cả các lược đồ phải không? Bằng cách này, isRunningTests sẽ hoạt động trong tất cả các lược đồ.
Vishal Singh

@VishalSingh Vâng, tôi tin rằng nó sạch hơn. Bạn đã thử cách tiếp cận đó chưa? Hãy cho chúng tôi biết nếu nó hoạt động tốt cho bạn.
idStar

16
var isRunningTests: Bool {
    return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}

Sử dụng

if isRunningTests {
    return "lena.bmp"
}
return "facebook_profile_photo.bmp"

Không giống như một số câu trả lời khác, câu trả lời này hoạt động. Cảm ơn bạn.
n13

8

Phương pháp kết hợp của @Jessy và @Michael McGuire

(Như câu trả lời được chấp nhận sẽ không giúp bạn trong khi phát triển một khuôn khổ)

Vì vậy, đây là mã:

#if DEBUG
        if (NSClassFromString(@"XCTest") == nil) {
            // Your code that shouldn't run under tests
        }
#else
        // unconditional Release version
#endif

1
Cái này đẹp hơn nhiều! Ngắn hơn và tương thích với khuôn khổ!
blackjacx

Đã làm việc trên một gói Swift và điều này làm việc cho tôi
D. Greg

4

Đây là cách tôi đã sử dụng trong Swift 4 / Xcode 9 cho các bài kiểm tra đơn vị của chúng tôi. Nó dựa trên câu trả lời của Jesse .

Không dễ để ngăn việc tải bảng phân cảnh, nhưng nếu bạn thêm phần này vào đầu didFinishingLaunching thì điều đó sẽ giúp các nhà phát triển của bạn hiểu rõ điều gì đang xảy ra:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions:
                 [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    #if DEBUG
    if let _ = NSClassFromString("XCTest") {
        // If we're running tests, don't launch the main storyboard as
        // it's confusing if that is running fetching content whilst the
        // tests are also doing so.
        let viewController = UIViewController()
        let label = UILabel()
        label.text = "Running tests..."
        label.frame = viewController.view.frame
        label.textAlignment = .center
        label.textColor = .white
        viewController.view.addSubview(label)
        self.window!.rootViewController = viewController
        return true
    }
    #endif

(rõ ràng bạn không nên làm bất cứ điều gì như thế này đối với các bài kiểm tra giao diện người dùng mà bạn muốn ứng dụng khởi động như bình thường!)


3

Bạn có thể truyền các đối số thời gian chạy vào ứng dụng tùy thuộc vào lược đồ ở đây ...

nhập mô tả hình ảnh ở đây

Nhưng tôi muốn hỏi liệu nó có thực sự cần thiết hay không.


3

Đây là cách nhanh chóng để làm điều đó.

extension Thread {
  var isRunningXCTest: Bool {
    for key in self.threadDictionary.allKeys {
      guard let keyAsString = key as? String else {
        continue
      }

      if keyAsString.split(separator: ".").contains("xctest") {
        return true
      }
    }
    return false
  }
}

Và đây là cách bạn sử dụng nó:

if Thread.current.isRunningXCTest {
  // test code goes here
} else {
  // other code goes here
}

Dưới đây là toàn bộ bài viết: https://medium.com/@theinkedengineer/check-if-app-is-running-unit-tests-the-swift-way-b51fbfd07989


Vòng lặp for có thể được Swiftized như thế này:return threadDictionary.allKeys.anyMatch { ($0 as? String)?.split(separator: ".").contains("xctest") == true }
Roger Oba

2

Một số cách tiếp cận này không hoạt động với UITest và nếu về cơ bản bạn đang thử nghiệm với chính mã ứng dụng (thay vì thêm mã cụ thể vào mục tiêu UITest).

Tôi đã kết thúc việc thiết lập một biến môi trường trong phương thức setUp của thử nghiệm:

XCUIApplication *testApp = [[XCUIApplication alloc] init];

// set launch environment variables
NSDictionary *customEnv = [[NSMutableDictionary alloc] init];
[customEnv setValue:@"YES" forKey:@"APPS_IS_RUNNING_TEST"];
testApp.launchEnvironment = customEnv;
[testApp launch];

Lưu ý rằng điều này là an toàn cho thử nghiệm của tôi vì hiện tại tôi không sử dụng bất kỳ giá trị khởi chạy nào khác; nếu bạn làm vậy, tất nhiên bạn sẽ muốn sao chép bất kỳ giá trị hiện có nào trước tiên.

Sau đó, trong mã ứng dụng của mình, tôi tìm kiếm biến môi trường này nếu / khi tôi muốn loại trừ một số chức năng trong quá trình thử nghiệm:

BOOL testing = false;
...
if (! testing) {
    NSDictionary *environment = [[NSProcessInfo processInfo] environment];
    NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
    testing = [isRunningTestsValue isEqualToString:@"YES"];
}

Lưu ý - cảm ơn vì nhận xét của RishiG đã cho tôi ý tưởng này; Tôi chỉ mở rộng điều đó thành một ví dụ.


1

Phương pháp tôi đang sử dụng đã ngừng hoạt động trong Xcode 12 beta 1. Sau khi thử tất cả các câu trả lời dựa trên bản dựng cho câu hỏi này, tôi đã được truyền cảm hứng bởi câu trả lời của @ ODB. Đây là phiên bản Swift của một giải pháp khá đơn giản, hoạt động cho cả Thiết bị thực và Trình mô phỏng. Nó cũng phải là "bằng chứng phát hành" khá.

Chèn vào thiết lập Thử nghiệm:

let app = XCUIApplication()
app.launchEnvironment.updateValue("YES", forKey: "UITesting")
app.launch()

Chèn vào ứng dụng:

let isTesting: Bool = (ProcessInfo.processInfo.environment["UITesting"] == "YES")

Để dùng nó:

    if isTesting {
        // Only if testing
    } else {
        // Only if not testing
    }

0

Đã làm cho tôi:

Objective-C

[[NSProcessInfo processInfo].environment[@"DYLD_INSERT_LIBRARIES"] containsString:@"libXCTTargetBootstrapInject"]

Nhanh: ProcessInfo.processInfo.environment["DYLD_INSERT_LIBRARIES"]?.contains("libXCTTargetBootstrapInject") ?? false


0

Đầu tiên hãy thêm biến để thử nghiệm:

nhập mô tả hình ảnh ở đây

và sử dụng nó trong mã của bạn:

 if ProcessInfo.processInfo.environment["IS_UNIT_TESTING"] == "1" {
                 // Code only executes when tests are running
 } 

0

Rõ ràng trong Xcode12, chúng ta cần tìm kiếm trong khóa môi trường XCTestBundlePaththay vì XCTestConfigurationFilePathnếu bạn đang sử dụngXCTestPlan

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.