Introduction to XCTest framework

Sandun Rajitha Liyanage
7 min readJul 5, 2022

--

First let’s ask the question, why tests? Doesn’t writing tests take longer? since you have to write the code and then write code for tests as well?

Well as the applications become more and more complex testing gives us the confidence that our code is correct. When a bug occurs we can write a test for that bug to make sure it doesn’t happen again. And tests will give us confidence and reassurance that if we refactor the code it’ll not break functionality.
Testing makes us write better code as well because if it’s really hard to test or can’t test at all it’s a sign that you code is not very modular or it’s too tightly coupled and probably needs to be refactored. Tests often becomes the documentation for your code.

What makes up a good test?

Repeatable — if you write a test and sometimes it passes and other times it fails. This doesn’t help us be confident in our code. So the tests should work as it’s supposed to, every time you run it.

Fast — Test should be fast to run. If you have a team of multiple developers and each of those developers are trying to merge their code into the master branch or a feature branch they’ll have to make sure all the tests passes. So having slow tests will prevent your team from moving fast. and it’ll add up over time.

Maintainable — If tests are hard to setup or hard to refactor when the code changes it’ll make the development process with tests harder than it should be and take much more extra time as it needs to.

Test Driven Development (TDD)

Test Driven Development cycle
Test Driven Development cycle

In TDD you write a test first, and this will be a failing test at the beginning because no code is written. After that you write the code to compile and fulfill the requirements so it passes the tests but it doesn’t need to be the most elegant or optimized code. We just want to make sure the test passes so we know that we have the expected behavior. From there you can start refactoring and optimizing your code as long as the tests passes. You continuously do this process as you build your test suite and the features.

Setting up unit tests

We’ll start by creating a new project. And for this demo we’ll continue with the default application option.

And simply select “Include Tests” from the project options window.

You’ll get 3 targets under the General tab. App Target, Test target and a UI test target.

What makes up a test

This is the anatomy of a test case,

  • First we import the XCtest framework, Then we import the target we want to test.
  • @testable gives us elevated access for that module in that scope.
    Classes and class members marked as public will behave as if they were marked open. Other entities marked as internal act as if they were declared public.
  • However fileprivate and private declarations are not visible outside of their usual scope even with @testable.
  • For each of the class we want to test we’ll subclass XCTestCase. Which is the primary class for defining test cases, test methods, and performance tests.

setUpWithError() — This method provides the opportunity to reset state and to throw errors before calling each test method in a test case.

tearDownWithError() — This method provides the opportunity to perform cleanup and to throw errors after each test method in a test case ends.

self.measure() — Is the way to test the performance of your code. Performance tests allows you to test how fast your code runs, but more importantly it allows you to test how fast your code runs as your code base evolves.

** When you are writing a test you have to start with the word “test” that’s how XCode determines it’s a test.

** If you want to write any helper functions and reuse them in multiple tests they should not start with “test”

** Typically a separate test file is created per source file.

Writing a test case

First of all when we add the tests at project setup it’ll automatically create a test file named with Project name. Usually we should delete this and create a new test file for each source file as we go along.

So we have removed the “CalculatorTests” class which was automatically created by XCode since it doesn’t match the class name we want to test.

Next we have created a class called “CalculatorAdd” with the simple add function.

Now wee need to add a test file for this class. So we will create a Unit Test Case Class

Set the same name as the source file we want to test and add Tests at the end.

And since we are using Swift we don’t need a bridging header.

Next we need to import our app target so we can access the classes of it. And we have to create a property for the CalculatorAdd class.

Next we’ll initialize the calculator in the setup method so the we have a fresh instance of CalculatorAdd before we run any tests. And we will set calculator property to nil so it’s deinit method is called after test finishes.

Now we can create a test method to test the add function of our class. Lets call it testAdd. inside this function we will call the calculators add method and keep the result in a variable.

Note: XCode offers us many different assertions that can be used to test various things like testing for nil, true or false, throws, doesnot throw, etc …

In this case we will use XCTAssertEqual here.

We have wrote our first test. now we can run it and see if it passes.

You can also add custom error messages if a test fails. We’ll see how to do that.

We’ve added our custom error message as above and it shows in the console when the test fails.

Setting up different test schemes

We have created another test file for CalculatorSubstract se we are going to see how we can create 2 separate test Schemese for CalculatorAddTests and CalculatorSubtractTests.

Having separate test schemes is useful when you have some test that would take some time to run like UI tests or integrations tests but you don’t need to always run them. Also when you want to have separate schemes for different features.

First select the New Scheme from the Scheme chooser

From the target dropdown we’ll select our test target and we’ll name this scheme “CalculatorAddTests” then click Ok.

and repeat the same for “CalculatorSubtractTests”

Next go to the edit scheme and from the Test tab we can remove or add different test for the target. For the “CalculatorAddTests” we will only keep the CalculatorAddTests. and do the same for “CalculatorSubtractTests” scheme.

Now when we select the CalculatorAddTests from the scheme chooser and go to the test navigator we’ll only have the testAdd method enabled. And when we select the CalculatorSubtractTests scheme, we’ll only have the testSubtract method.

Conclusion

In this post we discussed what makes a good test, how to add unit testing target, How to write basic tests, How to separate tests into multiple schemes.

I hope this post gives you the basic idea on how testing works with XCode so you can get started with XCtest and start your TDD journey.

Finally,

Although writing tests mean that you’ll take a longer time and mover slower at first, in the long run of supporting your software product you’ll spend less time tracking down bugs in production as well as have a higher quality code base than without tests. And you will be much more confident in refactoring your code or adding new components to your application.

--

--

Sandun Rajitha Liyanage

Software Engineer specializing in mobile application development.