preface

There are more tests than assertions. When do XCTest create and run tests? IOS programmers are particularly prone to making false assumptions about the test lifecycle. These assumptions can lead to errors in test design.

For example, we often encounter a test case that can pass the test when run alone, but fails in the portfolio test. In order to avoid unstable tests, we need to give the test a stable, clean environment.

The code in the test method is organized into three sections to identify these phases:

  • Arrange, Arrange, which is the part that defines all variables and models.
  • Action: The part that triggers the method under test and returns the result.
  • Assert: This is the part of evaluating expected results.

Remember AAA is an important part of unit testing

The use of XCTest

   func test_methodOne() {
        let sut = MyClass()

        sut.methodOne()

        // Normally, assert something
    }
Copy the code

The name SUT stands for the system under test and is often abbreviated to SUT. This is a common term for “what we’re testing.” Unlike this simple example, tests usually have many objects at work. Using a consistent name like SUt makes it clear which object the test will apply to. It also makes it easier to reuse test snippets.

XCTest tests the mechanism

  • At test time, XCTest searches for all classes inherited from XCTestCase.
  • For such a class, it will find every test method. The names of these methods start with test and have no arguments and no return value. Such asfunc test_methodOne()
  • For each such test method, it creates an instance of the class. With objective-C Runtime, it remembers which test method the instance will run.
  • XCTest collects instances of subclasses into the test suite.
  • When it has completed the creation of all the test cases, XCTest will start running them.
class MyClassTests: XCTestCase {
    private let sut = MyClass()

    func test_methodOne() {
        sut.methodOne()

        // Normally, assert something
    }

    func test_methodTwo() {
        sut.methodTwo()

        // Normally, assert something
    }
}
Copy the code
For example,

MyClassTests has two test methods test_methodOne and test_methodTwo. So, when XCTest runs, you’ll find MyClassTests. It searches for method names starting with test and finds two. So it creates two instances of MyClassTests: one running test_methodOne and the other running test_methodTwo.

usesetUp().tearDown()To optimize the

XCTestCase defines two methods, setUp and tearDown, that are designed to be overridden in subclasses.

The test runner in XCTest ensures that each test case is in the following order:

  • Call setUp(create object).
  • Call the test method.
  • Call tearDown.
class MyClassTests: XCTestCase {
    private var sut: MyClass!

    override func setUp() {
        super.setUp()
        sut = MyClass()
    }

    override func tearDown() {
        sut = nil
        super.tearDown()
    }

    func test_methodOne() {
        sut.methodOne()

        // Normally, assert something
    }

    func test_methodTwo() {
        sut.methodTwo()

        // Normally, assert something
    }
}
Copy the code

Note: Initializes the storage properties in the test class. Change these properties from let to VAR. And add!

Tips Detection test Log

As shown in the figure, we can quickly find the failure message of test

How to write good tests in a project?

Code test coverage

From the Xcode menu, select Product ▶ Scheme ▶ Edit Scheme… Or press ⌘ – <.

We now have test coverage set up

⌘ + U to try

And from the Xcode menu, select Editor ▶ Code Coverage.

The numbers in red represent how many times we tested this code, in the red stripes. Hover the mouse cursor over the area and you will see the situation change, as shown below:

The green section shows the code we worked with. The line with the else is partly green and partly red. This gives us a way to look at inline code coverage.

Add tests to existing code

For example,

class CoveredClass {

    static func max(_ x: Int, _ y: Int) -> Int {
        if x < y {
            return y
        } else {
            return x
        }
    }
}
Copy the code

If the code is in use, we don’t need to work backwards from the requirements. Instead, we can write something that effectively uses legacy code called a feature test. These are tests that capture the actual behavior of your code.

To write a feature test, do the following:

  • Calling code from a test produces some result.
  • Write an assertion that compares the result to a value that you know doesn’t match.
  • Run the tests. The failure message will tell you the actual result.
  • Adjust the assertion so that it expects the actual result.
  • Rerun the test to see if it passes.

Part 1, 2,

    func test_max_with1And2_shouldReturnSomething() {
        let result = CoveredClass.max(1, 2)

        XCTAssertEqual(result, -123)
    }
Copy the code

3. Run the tests. This gives us a failure message

4. We copy the actual value 2 from the failure message and paste it into the assertion

    func test_max_with1And2_shouldReturn2() {
        let result = CoveredClass.max(1, 2)

        XCTAssertEqual(result, 2)
    }
Copy the code

5. Run

Our test is complete!! Through ~

Of course, if you want better test coverage, let’s add a test to cover the second half. If x is less than y

    func test_max_with3And2_shouldReturn3() {
        let result = CoveredClass.max(3, 2)

        XCTAssertEqual(result, 3)
    }
Copy the code

This should give us 100% coverage. But in practice, 100% coverage is difficult to achieve because of an}

You can achieve 100% coverage by writing a few more, but you shouldn’t be a slave to test coverage.

Tips

A good test name consists of three parts:

  • What the test is about. This is usually a function name.
  • Test conditions. What are the different inputs?
  • Expected results.