automation is ios development
TRANSCRIPT
Автоматизация в разработке
Deus Ex
Когда?
Когда?• Повторяющиеся задачи
Почему?
Почему?
• Время• Ресурсы
Что?
Что?
• Сборка
Сборка
+ Настраивается все
xcodebuild
xcodebuild \ -scheme MyiOSApp \ SYMROOT=«/Users/username/DebugLocation"
Сборка
+ Настраивается все
xcodebuild
- Настраивается всеxcodebuild \
-workspace ./SomeProject.xcworkspace \-scheme SomeProject-iOS \-sdk iphonesimulator \-destination 'platform=iOS Simulator,id=6F7F1DB6-EC1C-472A-80D3-28FA72C9F70A' \CODE_SIGN_IDENTITY="${IDENTITY}" \
OTHER_CODE_SIGN_FLAGS="--keychain ${KEYCHAIN}" \ ....
test
Сборка
+ Настраивается все
xcodebuild
- Настраивается все- Нечитаемая выдача
CompileC /Users/skyer/Library/Developer/Xcode/DerivedData/ReactiveCocoa-dncnleuvdlfncahcublwmkwlvdpq/Build/Intermediates/CodeCoverage/ReactiveCocoa-iOS/Intermediates/ReactiveCocoa.build/Debug-iphonesimulator/ReactiveCocoa-iOSTests.build/Objects-normal/x86_64/RACDelegateProxySpec.o ReactiveCocoaTests/Objective-C/RACDelegateProxySpec.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
cd /Users/skyer/Desktop/work/ReactiveCocoa export LANG=en_US.US-ASCII export PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/
sbin:/sbin" /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x objective-c -arch x86_64 -fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-
backtrace-limit=0 -std=gnu99 -fobjc-arc -fmodules -gmodules -fmodules-cache-path=/Users/skyer/Library/Developer/Xcode/DerivedData/ModuleCache -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -fbuild-session-file=/Users/skyer/Library/Developer/Xcode/DerivedData/ModuleCache/Session.modulevalidation -fmodules-validate-once-per-build-session -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module -Wno-trigraphs -fpascal-strings -O0 -fno-common -Werror -Wmissing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wunreachable-code -Wno-implicit-atomic-properties -Werror=deprecated-objc-isa-usage -Werror=objc-root-class -Wno-arc-repeated-use-of-weak -Wduplicate-method-match -Wmissing-braces -Wparentheses -Wswitch -Wunused-function -Wunused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wno-shadow -Wfour-char-constants -Wconversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wsign-compare -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno-selector -Wno-strict-selector-match
. . .
. . .
Сборка
+ Настраивается все
xcodebuild + xcpretty
- Настраивается все
▸ Check Dependencies▸ Processing Info.plist▸ Compiling ResultType.swift▸ Compiling Result.swift▸ Compiling Result_vers.c▸ Linking Result▸ Copying Result.h
Сборка
+ Просто+ Параллельное выполнение тестов- Иногда не работает
xctool
Сборка
+ Проще некуда+ Обертка над xcodebuild+ Часть fastlane- Иногда не работает- Огромный и не очень прозрачный тулсет
fastlane scan
Что?
• Сборка • Тестирование
Пишите тесты
Заведите себе CI
Тестирование
• Build bot
CI + Tests
Тестирование
• Build bot• Branch + cron• Pull request• Уведомления
CI + Tests
Тестирование
• Уведомления
CI + Tests
Тестирование
• Уведомления
CI + Tests
Тестирование
• Уведомления
CI + Tests
Что?
• Сборка • Тестирование• Подготовка ресурсов
Ресурсы
• Изображения• Строки• UI
Изображения
• Из больших картинок собрать @1x, @2x, …• Оптимизация ресурсов• Валидация ресурсов
ImageMagick
ИзображенияZeplin
ИзображенияSwiftGen
Кодогенерация
Кодогенерация
• Асинхронные процессы• Непредсказуемое качество• Емкость задач
Когда?
Кодогенерация
• Статическое описание• Compile-time валидация• Делегирование
Зачем?
Кодогенерация
• Во время сборки проекта
Когда?
ИзображенияSwiftGen
let image1 = UIImage(asset: .Banana)let image2 = UIImage.Asset.Apple.image
extension UIImage { enum Asset : String { case GreenApple = "Green-Apple" case RedApple = "Red-Apple" case Banana = "Banana" case BigPear = "Big_Pear" case StopButtonEnabled = "stop.button.enabled"
var image: UIImage { return UIImage(named: self.rawValue)! } }}
Ресурсы
• Изображения• Строки• UI
СтрокиLAAS
• Single source of truth• Мы не любим их редактировать
СтрокиSwiftGen
enum L10n { case AlertTitle case AlertMessage case Greetings(String, Int) case ApplesCount(Int) case BananasOwner(Int, String)}
extension L10n : CustomStringConvertible { var description : String { return self.string }
var string : String { /* Implementation Details */ }}
func tr(key: L10n) -> String { return key.string}
СтрокиStatic strings
• Статические строки• Валидация локализации• Тестируемость без рефлексии
Ресурсы
• Изображения• Строки• UI
UISwiftGen
let validateVC = StoryboardScene.Wizard.ValidatePassword.viewController()let createVC = StoryboardScene.Wizard.createAccountViewController()
override func prepareForSegue(_ segue: UIStoryboardSegue, sender sender: AnyObject?) { switch StoryboardSegue.Message(rawValue: segue.identifier)! { case .Back: // Prepare transition case .Forward: // Prepare transition }}
initialVC.performSegue(StoryboardSegue.Message.Back)
Что?
• Сборка • Тестирование• Подготовка ресурсов• Разработка (или все остальное)
Все остальное?
• Кодогенерация тестовых файлов• Mocked backend• DSL
Кодогенерация 2Что?
. . .extension SignUpEmailViewModel.Model { static func mock( result result: Action<SignUpStepResult<SignUp.Email>, SignUp.Email, NoError> = Action { _ in .empty }, stringsService: StringsService = Strings.mockedLocalized, analytics: AnalyticsService = { _ in } ) -> SignUpEmailViewModel.Model { return SignUpEmailViewModel.Model( result: result, stringsService: stringsService, analytics: analytics ) }}
extension SignUpEmailViewModel {
final class TestView: TestViewType { let navigationTitle = ValueTestView<String>() let emailDescription = ValueTestView<String>() let emailPlaceholder = ValueTestView<String>() let emailClearButtonMode = ValueTestView<UITextFieldViewMode>() let status = ValueTestView<String>() let statusStyle = ValueTestView<StatusStyle>() let stepsLabel = ValueTestView<String>() let originalEmail = ValueTestView<String>() let emailSink = ValueTestView<String -> ()>() let nextActive = ValueTestView<Bool>() let next = ValueTestView<Action<Void, Void, NoError>>() let nextTitle = ValueTestView<String>() let keyboardFocus = ValueTestView<FocusType>() let keyboardType = ValueTestView<UIKeyboardType>() let keyboardReturnKeyType = ValueTestView<UIReturnKeyType>() let touchOutsideSink = ValueTestView<SimpleAction>() let back = ValueTestView<Action<Void, Void, NoError>>() let willDisappear = ValueTestView<AppearanceAction>()
let disposable: ScopedDisposable? init(_ viewModel: SignUpEmailViewModel) { }
}}. . .
struct SignUpEmailViewModel: ViewModelType { struct Model { let result: Action<SignUpStepResult<SignUp.Email>, SignUp.Email, NoError> let stringsService: StringsService let analytics: AnalyticsService } struct Presenters { let navigationTitle: Presenter<String> let emailDescription: Presenter<String> let emailPlaceholder: Presenter<String> let emailClearButtonMode: Presenter<UITextFieldViewMode> let status: SerialPresenter<String> let statusStyle: SerialPresenter<StatusStyle> let stepsLabel: Presenter<String> let originalEmail: Presenter<String> let emailSink: Presenter<String -> ()> let nextActive: SerialPresenter<Bool> let next: Presenter<Action<Void, Void, NoError>> let nextTitle: Presenter<String> let keyboardFocus: SerialPresenter<FocusType> let keyboardType: Presenter<UIKeyboardType> let keyboardReturnKeyType: Presenter<UIReturnKeyType> let touchOutsideSink: Presenter<SimpleAction> let back: Presenter<Action<Void, Void, NoError>> let willDisappear: Presenter<AppearanceAction> }
. . .}
Кодогенерация 2SourceKitten
• Это как SourceKit, только котик• Машино-читаемое AST
Кодогенерация 2Swift?
А почему бы и нет?
Кодогенерация 2
• pod CommandLine./Scripts/vmGen \
-t ./Scripts/vmSpecGen/VMSpecTemplate.swift -i
./App/Screens/SignUpStory/SignUpEmailViewModel.swift
Кодогенерация 2
• pod Stencil/Mustacheextension {{ vm.name }} static func mock(
)
{% for modelItem in vm.modelItems %} {{ modelItem.title }}:
{{ modelItem.type }},{% endfor %}
. . .
Все остальное?
• Кодогенерация тестовых файлов• Mocked backend• DSL
Отладка HTTPЧто?
• Большое количество network edge-cases• Инфраструктура автотестов?• Костыли в приложении
Отладка HTTPЧем?
• Charles Proxy• Mitm proxy• Backend• Свое решение
Отладка HTTPNodeJS?
brew install nodenpm install http-proxynpm install JSONStreamnpm install event-stream
Отладка HTTPSwift? Caramel!
• package Caramel• libuv• Переиспользование кодовой базы
Что дальше?
• Swift Equatable?• JSON Schema?• CI visualisation?• …
Post scriptum
• Любите своих коллег
Post scriptum
• Любите своих коллег• Но для этого вам придется любить bash
brew install sourcekittenxcodebuild -configuration Release -project Parser.xcodeproj -scheme Parser CONFIGURATION_BUILD_DIR='./build'rm -rf ../vmGenmv ./build/Parser ../vmGenrm -rf ./build
bold=$(tput bold)normal=$(tput sgr0)
echo "\n\n\n${bold}created file vmGen at App/Scripts"echo "it ignored by git, so don't be afraid to rebuild it${normal}"echo "\n\npattern of usage from App root\n\n ./Scripts/vmGen -t ./Scripts/vmSpecGen/VMSpecTemplate.swift -i ./App/Screens/SignUpStory/SignUpEmailViewModel.swift"
Вопросы?