In Xcode 8, what is the way to make test methods run in a particular order within a given XCTestCase class?
Asked Answered
C

1

5

Prior to Xcode 8, you could get your unit tests to run in a particular order by alphabetizing the names of the test methods within a given XCTestCase class (as described in this answer). E.g., tests would run like: testA, testB, testC, testD, etc.

However in Xcode 8, this is no longer the case. For example I have test methods named test1, test2, test3, test4, and test4 will run first (see below screenshot). Then I can re-run, and test2 will run first on the next run-through.

Xcode test navigator screenshot

So how do I get the tests to run in order now on Xcode 8?

Ceramic answered 20/9, 2016 at 19:51 Comment(10)
As said in the comments of the Q&A you linked to, unit tests are supposed to be able to run independently of any other tests – why do you need to order them?Nosebleed
They're not unit tests, they're sequential tests in an end-to-end test. Why do they have to be unit tests necessarily? Not all tests are unit tests in this world.Ceramic
Anyway for the time being I made it all into one big test by using observers and selectors, completion blocks, etc. You will find that there are cases where big end-to-end tests can be very valuable in addition to standard unit tests. But if each XCTestCase method is supposed to represent a single "unit" without dependencies on other tests then, well, it is what it is. I'm just not sure if it's a bug or not.Ceramic
Check the section Creating Tests Programmatically in developer.apple.com/reference/xctest/xctestcase. I wouldn't expect Xcode integration with this approach, but you might be able to get something working.Mcnair
@CommaToast, good unit testing frameworks will randomize the test order to make sure you don't have strange dependencies in your code (a good source of bugs).Gabardine
@CommaToast, if you give an example of why you need the dependencies between test, we can show you how to write the tests without dependencies.Gabardine
I have some integration/UI tests that require some setup to run. I currently have the setup in tests that need to run in a particular order, i.e. log in the user before running the tests. This change will break those setup tests so I will need to find a solution.Imitation
@Gabardine I have the same issue as Alex. We make a point of sale application. The test simulates user opens a till, runs a bunch of tickets (transactions) based on data in an Excel spreadsheet that is read into the test using a cool Excel-file-reading framework, then the app syncs all the data of the tickets to our test server, then deletes its local copies and syncs all the data back down and makes sure it's the same as before it synced, then it closes the till and makes sure its math on all the totals are correct. Obviously, alot of this stuff should have unit tests around it as well.Ceramic
But we like the end to end test because it simulates how a real user would actually use the app and it has been invaluable in helping us find tough issues related to multi-threading where multiple things are going on at the same time, that unit testing simply could never see because the units are the things that are on their own threads and the unit tests cannot deal with what's going on in the other threads etc.Ceramic
I'm in the same boat where a defined set of UI tests needs to run in a specific sequence. You can't get to a landing page without handling the login page first. Has nothing to do with Unit testing as it does end-to-end. I'm at a loss right now and don't want to create 10 test targets to perform a sanity test.Popper
C
6

So the way I solved this is as follows.

Problem recap:

I needed to have several tests run in a row: test1, test2, test3, test4. Each test set up an expectation and the final step in the test would fulfill the expectation, then the test would conclude and the next one would run.

However in Xcode 8, tests now run in random order. While that's good from the standpoint that, if they are unit tests, they should be able to run in random order, it breaks your tests if they are designed not as unit tests, but as end-to-end tests.

For example in my case it breaks the tests because in the first test, a user logs in and set up some data etc. Then, the second test checks the math, then the third test syncs the data to a server, then the fourth one deletes it all and syncs down from the server. When the first test runs, at build time, a shell script inits the server's DB from a MSYQL file, then at launch time of the app, the AppDelegate installs a fresh Core Data DB for the app. So, if I have to launch the app fresh after each test, the shell script will re-init the server DB and cause the app's local Core Data DB to also re-init. This will break the subsequent tests (it being an end-to-end test the subsequent tests depend on the state of the app and server being a certain way, after the previous test ran).

Rather than set up four different Core Data starting DBs and four different server init scripts (which would have been a huge pain and made the end-to-end test exponentially more time consuming to manage whenever we have a schema change), or have to remember to run without building each test manually in a row, instead I merged all four test methods into a single really long test method using the following tactic.

Solution

First, in the XCTestCase class, I set up an test expectation property:

@property (nonatomic, strong) XCTestExpectation *endOfTestExpectation;

At the end of the test1 in my XCTestCase class, I replaced its existing expectation with this new expectation, like so:

self.endOfTestExpectation = [self expectationWithDescription:
                                  @"endOfTestExpectation"];

[self waitForExpectationsWithTimeout:900 
                             handler:^(NSError * _Nullable error) {
    /* Code moved from test4's expectation completion block goes here */
}

For each test1 through test3, I moved the code that was inside the test's original expectation completion block into a new method called completion1 through completion3. For test4 I moved the code inside its original expectation completion block into the endOfTestExpectation's completion block at the end of the test1 method.

Then I renamed the methods test2 through test4 to be named t3st2 through t3st4 (quick and dirty, I know; you should pick something more descriptive after you get it working). At the end of the completion1 method, I call t3st2; at the end of completion2 I call t3st3; at the end of completion3 I call t3st4; and at the end of completion4 I call [self.endOfTestExpectation fulfill];.

This actually ends up being better than the old way, because in the old way, even if the first test failed, the subsequent tests would still run! Now, wherever an XCTFail happens, the entire thing just stops and we don't waste time running the rest if I was tabbed over into SO :D

Ceramic answered 22/9, 2016 at 19:10 Comment(2)
Thanks for looking into this. Would you happen to know how to do this in Swift?Popper
The old way you could use continueAfterFailure = true in an override func setUp(), but anyways it looks like I have to use the expectation() now : ( more codePopper

© 2022 - 2024 — McMap. All rights reserved.