Как приложение ios знает, что нужно запускать модульные тесты

Я знаю, что могу запустить модульные тесты своего приложения с помощью xcodebuild, но мне интересно, что говорит приложению запускать тесты во время запуска, это специальный аргумент, отправленный приложению, или он скомпилирован по-другому для запуска тестов (с XCTest)?


person Pablo Retyk    schedule 24.07.2014    source источник
comment
Разве Xcode не запускает xctest для запуска тестов, а не приложения? developer.apple.com/library/ mac/документация/Дарвин/Справочник/   -  person trojanfoe    schedule 25.07.2014


Ответы (2)


Xcode использует сценарии xctest и RunTargetUnitTests (из /Developer/Tools/RunTargetUnitTests) для модульного тестирования.

xctest внедряет dynamic library (набор для тестирования, который имеет отдельный поток для своих задач) в ваш двоичный файл, инъекция дает dylib доступ к вашей памяти процесса (он может получить доступ к вашим классам/экземплярам в вашей памяти процесса), и он может выполнять вызовы и создавать единицы тесты. Обратные вызовы от устройства/симулятора принимаются от отладчика (специальной техники модульного тестирования нет).

Проще говоря: проект с test scheme компилируется как обычно, но это линковка динамической библиотеки, которая будет троянить память вашего процесса, и делать тесты.

Также очень полезная информация:

RunUnitTests принимает ENVIRONMENT variables, вот некоторые интересные из них

TEST_HOST — полный путь к исполняемому файлу, в который нужно «внедрить» указанный пакет модульных тестов. Для приложения это должен быть полный путь к приложению в его оболочке. Не устанавливайте это для каркасных работ или библиотек.

TEST_RIG — полный путь к исполняемому файлу для использования в качестве тестовой установки вместо CPlusTestRig или otest. Исполняемый файл должен указать путь к тестовому пакету в качестве последнего аргумента. Его DYLD_FRAMEWORK_PATH и DYLD_LIBRARY_PATH будут настроены так, чтобы они указывали на BUILT_PRODUCTS_DIR перед выполнением. Не устанавливайте это, если вы используете один из тестовых стендов по умолчанию.

также
BUNDLE_LOADER используется как параметр компоновщика, который указывает компоновщику связать Testing dynamic library с указанным двоичным файлом.

Целевые шаблоны тестовых пакетов имеют фазу сборки сценария оболочки в самом конце, которая вызывает /Developer/Tools/RunUnitTests. RunUnitTests просматривает параметры сборки, которые он передает через свою среду, и определяет на основе этой информации, как запускать тесты в вашем тестовом пакете.

Если вы тестируете фреймворк, RunUnitTests запустит соответствующую тестовую установку и скажет ей загрузить и запустить тесты в вашем пакете. Поскольку ваш тестовый пакет должен быть связан с вашим фреймворком, ваш фреймворк будет загружен, когда тестовая установка загрузит ваш пакет.

Если вы тестируете приложение, вам необходимо указать приложение в качестве тестового хоста и загрузчика пакетов для вашего тестового пакета в настройках сборки его конфигурации. Параметр Bundle Loader указывает компоновщику связать ваш пакет с приложением, которое его загружает, как если бы приложение было фреймворком, что позволяет вам ссылаться на классы и другие символы в приложении из вашего пакета, фактически не включая их в пакет. Параметр Test Host указывает RunUnitTests запустить указанное приложение и внедрить в него тестовый пакет, чтобы запустить его тесты.

Дополнительную информацию см. на man страницах 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

Вот скрипт RunTargetUnitTests

#!/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
person l0gg3r    schedule 31.07.2014
comment
Интересно, как это работает. Однако я не вижу, как xctest/otest вызывается скриптом RunTargetUnitTests, и я не вижу никаких инъекций dylib. - person Droppy; 31.07.2014
comment
@Droppy, это была ошибка (я обновил сообщение), dynamic libraries вводятся с помощью инструмента otest/xctest. otest/xctest вызываются из Xcode. - person l0gg3r; 31.07.2014
comment
Пожалуйста, добавьте параметры компиляции TEST_RIG, TEST_HOST, а также BUNDLE_LOADER в свой ответ, и вы получите мой голос.) - person Eimantas; 31.07.2014
comment
Я не думаю, что otest или xctest вообще вызываются для iOS. - person Sulthan; 31.07.2014
comment
Сценарий RunUnitTests устарел в Xcode 7: RunUnitTests is obsolete. To run unit tests for your target, use the Test scheme action in the Xcode IDE and the test action in xcodebuild. - person jmkk; 22.09.2015

Я знаю, как это работало с OCTest, и я полагаю, что XCTest работает аналогичным образом.

Раньше я запускал свои тесты с помощью WaxSim, утилиты, которая обращается к частным платформам симулятора. Использование WaxSim здесь не очень важно, оно просто позволяет нам осмотреть внутренности.

Команда запуска выполнялась MyApp.app в симуляторе. Чтобы запустить тесты, их нужно сначала залить (они скомпилированы в отдельный бандл!). Команда выглядела так

WaxSim \
    -e DYLD_INSERT_LIBRARIES="$INJECTION_FRAMEWORK_PATH" \
    -e XCInjectBundle="MyApp.octest" \
    -e XCInjectBundleInto="MyApp.app/MyApp" \
    MyApp.app \
   -SenTest All

Где INJECTION_FRAMEWORK_PATH был определен следующим образом:

XCODE_PATH=$( xcode-select --print-path )
PLATFORM_PATH="$XCODE_PATH/Platforms/iPhoneSimulator.platform"
INJECTION_FRAMEWORK_PATH="$PLATFORM_PATH/Developer/Library/PrivateFrameworks/IDEBundleInjection.framework/IDEBundleInjection"

Также обратите внимание на параметры командной строки -SenTest All, которые сообщали фреймворку, какие тесты запускать.

По сути, приложение работало как всегда, но был добавлен еще один пакет для запуска тестов один за другим, а затем выход из всего приложения.

person Sulthan    schedule 31.07.2014