Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How the ios app "knows" to run the unit tests

I know that I can start my app's unit tests with xcodebuild, but I am wondering what tells the application to run the tests during the launch, is it a special argument sent to the application or it compiled differently in order to run the tests (with XCTest)?

like image 277
Pablo Retyk Avatar asked Jul 24 '14 12:07

Pablo Retyk


People also ask

How are iOS apps tested?

a) System Testing: This type of iOS testing is performed on the system to check if the various components of the system work together. In this testing process, the iOS application is launched on a real Apple device followed by its interaction with the user interface to trigger a specific set or sets of user action(s).

How does unit testing work in mobile application?

Unit tests have two main purposes: Reduce the number of bugs by making sure that our class/module/component is working as expected with all possible inputs. Reduce the number of regressions by making sure that our new functionality, bug fixes or refactoring hasn't broken any existing functionality.

How does unit testing works?

Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. This testing methodology is done during the development process by the software developers and sometimes QA staff.


1 Answers

Xcode uses xctest and RunTargetUnitTests script (from /Developer/Tools/RunTargetUnitTests) for unit testing.

xctest injects dynamic library (testing kit which has separate thread for it's jobs) into your binary, injection gives the dylib access to your process memory (it can access your Classes/Instances in you process memory), and it's able to perform calls and make unit tests. Callbacks from device/simulator are received from debugger(no special technique for unit testing).

Simply said: The project with test scheme is compiled as usual, but it's linking dynamic library which will trojan your process memory, and make tests.

Also info that will be very helpful:

RunUnitTests accepts ENVIRONMENT variables, here are some interesting of them

TEST_HOST - The full path to an executable into which to "inject" the specified unit test bundle. For an application, this must be the full path to the application within its wrapper. Do not set this for frame works or libraries.

TEST_RIG - The full path to an executable to use as a test rig instead of either CPlusTestRig or otest. The executable must take a path to a test bundle as its final argument. Its DYLD_FRAMEWORK_PATH and DYLD_LIBRARY_PATH will be configured to point to the BUILT_PRODUCTS_DIR prior to execution. Do not set this if you are using one of the default test rigs.

also
BUNDLE_LOADER is used as a linker option, which indicates linker to link Testing dynamic library into your specified binary.

The test bundle target templates have a shell script build phase at the very end that invokes /Developer/Tools/RunUnitTests. RunUnitTests looks at the build settings it's passed via its environment and determines from that information how to run the tests in your test bundle.

If you're testing a framework, RunUnitTests will run the appropriate test rig and tell it to load and run the tests in your bundle. Since your test bundle should link against your framework, your framework will be loaded when the test rig loads your bundle.

If you're testing an application, you need to specify the application as the Test Host and Bundle Loader for your test bundle in its configuration's build settings. The Bundle Loader setting tells the linker to link your bundle against the application that's loading it as if the application were a framework, allowing you to refer to classes and other symbols within the application from your bundle without actually including them in the bundle. The Test Host setting tells RunUnitTests to launch the specified application and inject your test bundle into it in order to run its tests.

For more information see man pages of RunTargetUnitTests/xctest

https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man1/RunTargetUnitTests.1.html#//apple_ref/doc/man/1/RunTargetUnitTests

https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man1/xctest.1.html

Here is the RunTargetUnitTests script

#!/bin/sh

#
# Copyright (c) 2005-2013 Apple Inc.  All rights reserved.
#

# Copyright (c) 1997-2005, Sen:te (Sente SA).  All rights reserved.
#
# Use of this source code is governed by the following license:
# 
# Redistribution and use in source and binary forms, with or without modification, 
# are permitted provided that the following conditions are met:
# 
# (1) Redistributions of source code must retain the above copyright notice, 
# this list of conditions and the following disclaimer.
# 
# (2) Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' 
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
# IN NO EVENT SHALL Sente SA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# 
# Note: this license is equivalent to the FreeBSD license.
# 
# This notice may not be removed from this file.

if [ "${NATIVE_ARCH_ACTUAL}" = "" ]; then
    NATIVE_ARCH_ACTUAL=`arch`
fi

if [ "${ARCHS}" = "" ]; then
    ARCHS=`arch`
fi

if [ "${DEVELOPER_DIR}" = "" ]; then
    DEVELOPER_DIR="${SYSTEM_DEVELOPER_DIR}"
fi 

if [ "${OTEST}" = "" ]; then
    OTEST="${DEVELOPER_DIR}/Tools/otest"
fi

if [ "${OTEST_TARGET}" = "" ]; then
    OTEST_TARGET="${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}"
fi

RunTargetUnitTestsForArch() {
    echo "${DEVELOPER_DIR}/Tools/RunTargetUnitTests:0: note: Starting tests for ${1}"

    if [ "${DYLD_FRAMEWORK_PATH}" = "" ] ; then
        DYLD_FRAMEWORK_PATH="${BUILT_PRODUCTS_DIR}:${DEVELOPER_DIR}/Library/Frameworks"
    else
        DYLD_FRAMEWORK_PATH="${BUILT_PRODUCTS_DIR}:${DEVELOPER_DIR}/Library/Frameworks:${DYLD_FRAMEWORK_PATH}"
    fi

    export DYLD_FRAMEWORK_PATH

    echo "OTEST=${OTEST}"

    arch -arch "${1}" "${OTEST}" "${OTEST_TARGET}"

    unset DYLD_FRAMEWORK_PATH

    echo "${DEVELOPER_DIR}/Tools/RunTargetUnitTests:0: note: Completed tests for ${1}"
}

SkipTargetUnitTestsForArch() {
    echo "${DEVELOPER_DIR}/Tools/RunTargetUnitTests:0: note: Skipped tests for ${1}"
}

if [ "${TEST_AFTER_BUILD}" = "YES" ]; then
    # Run the unit tests once per requested and supported architecture.
    for TEST_ARCH in ${ARCHS}; do
        case "${NATIVE_ARCH_ACTUAL}" in
        i386)
            if [ "${TEST_ARCH}" = "i386" ]; then
                RunTargetUnitTestsForArch "${TEST_ARCH}"
            else
                SkipTargetUnitTestsForArch "${TEST_ARCH}"
            fi
            ;;

        x86_64)
            if [ "${TEST_ARCH}" = "i386" -o "${TEST_ARCH}" = "x86_64" ]; then
                RunTargetUnitTestsForArch "${TEST_ARCH}"
            else
                SkipTargetUnitTestsForArch "${TEST_ARCH}"
            fi
            ;;

        *)
            RunTargetUnitTestsForArch "${TEST_ARCH}"
            ;;
        esac
    done
fi
like image 200
l0gg3r Avatar answered Sep 24 '22 01:09

l0gg3r