{"id":20619,"date":"2021-02-18T20:00:04","date_gmt":"2021-02-18T19:00:04","guid":{"rendered":"https:\/\/www.inovex.de\/blog\/?p=20619"},"modified":"2022-12-01T12:25:13","modified_gmt":"2022-12-01T11:25:13","slug":"automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/","title":{"rendered":"Automatic Quality Assurance Measures in an iOS\/tvOS Project with Fastlane, Danger and SwiftLint"},"content":{"rendered":"<p>Are you working in a development team on an extensive and dynamic iOS project? Then Fastlane, Danger, SwiftLint and GitLab can help you to ensure quality and save time.<!--more--><\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_79_2 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\"><\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#Introduction\" >Introduction<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#Automatic-checks-in-the-merge-request\" >Automatic checks in the merge request<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#Continuous-Integration-with-Fastlane-and-Gitlab-CI\" >Continuous Integration with Fastlane and Gitlab CI<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#SwiftLint\" >SwiftLint<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#Danger\" >Danger<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#Configuration-Bringing-it-all-together\" >Configuration: Bringing it all together<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#In-action-An-example-merge-request\" >In action: An example merge request<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#What-are-the-advantages\" >What are the advantages?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#Multiple-Devices-Test\" >Multiple Devices Test<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#Advantages-of-the-Multiple-Devices-Test\" >Advantages of the Multiple Devices Test<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#Thats-it\" >That&#8217;s it ?<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Introduction\"><\/span>Introduction<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Waipu.tv is an IPTV video streaming platform that has become the market leader within two years. Its vision is to continuously improve the product through constant innovation and the incorporation of customer feedback. We had the pleasure to support waipu.tv with the development of the iOS \/ tvOS apps and it was a lot of fun.<\/p>\n<p>The implementation of the video streaming app waipu.tv for iOS (iPhones)\u00a0began in autumn 2015. Over time, iPadOS (Universal App) and later tvOS (Apple TV) were added to the supported platforms.<\/p>\n<p>Since the beginning of the development (then Swift 1, iOS 7 \/ iOS 8 &#8211;\u00a0 today Swift 5 &#8211; iOS 11- iOS 14) the code base has been growing and changing constantly.<\/p>\n<p>This is caused by:<\/p>\n<ul>\n<li>New features<\/li>\n<li>Bug fixes<\/li>\n<li>Refactoring of existing code<\/li>\n<li>Updates and maintenance of connected services<\/li>\n<li>New major versions of iOS, iPadOS, tvOS<\/li>\n<li>Updates to the version of the Swift programming language<\/li>\n<li>Integration and updates of 3rd party SDKs<\/li>\n<\/ul>\n<p>We develop in two-week sprints at the end of which a new version of the apps is created. In order to ensure the quality of the rapidly growing code base and to keep the number of bugs low, the development team has introduced various automated measures beyond standard Unit- and UI-Tests, which are described below.<\/p>\n<p class=\"p1\"><span class=\"s1\">Since every code change finds its way to the <code>main<\/code>-branch of the git repository via a merge request, we have implemented various mechanisms at this point in order to carry out basic checks automatically.<\/span><\/p>\n<h2><span class=\"ez-toc-section\" id=\"Automatic-checks-in-the-merge-request\"><\/span>Automatic checks in the merge request<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We use <a href=\"https:\/\/about.gitlab.com\">GitLab<\/a>\u00a0on premise as version control system. It is entirely\u00a0based on <a href=\"https:\/\/git-scm.com\">Git<\/a> and, among many other things, offers options for code reviews. New features and bug fixes are developed on feature branches.\u00a0<span class=\"s1\">As soon as a new feature has been developed, it is made available as a merge request for review by at least one developer.<\/span><\/p>\n<p>When creating a merge request, developers should be aware that a careful review entails a time investment for the reviewer and the code should meet team standards.<\/p>\n<p>The following part explains how compliance with the team conventions can be checked using automatic checks and consequently a basic quality of the merge request can be ensured.\u00a0<span class=\"s1\">This saves the team unnecessary discussions (and time) in merge requests, e.g. about formatting or missing changelog entries, as the rules are defined before and automatically checked.\u00a0<\/span><\/p>\n<h2><span class=\"ez-toc-section\" id=\"Continuous-Integration-with-Fastlane-and-Gitlab-CI\"><\/span>Continuous Integration with Fastlane and Gitlab CI<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p class=\"p4\"><span class=\"s1\">We use <a href=\"https:\/\/fastlane.tools\">Fastlane<\/a>\u00a0for build, test, release and code signing.\u00a0\u00a0<\/span><span class=\"s1\">The configuration takes place in the <code>Fastfile<\/code>, in which so-called \u201cLanes\u201c are defined.<\/span><\/p>\n<p class=\"p1\"><span class=\"s1\">A \u201cRelease-Lane&#8220; could, for example, consist of the following sub-steps:<\/span><\/p>\n<ul>\n<li>Run UnitTests and UITests<\/li>\n<li>Make a release build of the app<\/li>\n<li>Sign the app<\/li>\n<li>Upload the app to App Store Connect<\/li>\n<\/ul>\n<p>A detailed description of continuous integration with fastlane and Gitlab-CI can be found in <a href=\"https:\/\/www.inovex.de\/blog\/continuous-delivery-ios-apps-gitlab-ci-fastlane\/\">this blog article<\/a> by Sebastian.<\/p>\n<p>As soon as a new merge request is created GitLab triggers a pipeline that compiles the merge request code via Fastlane and executes unit tests. The build itself takes place on a separate macOS build server on which a GitLab Runner is active. It receives and executes the build jobs. If an error occurs when compiling or executing the tests of a merge request, it cannot be merged.\u00a0This ensures that only compilable, unit-tested code can be merged.<\/p>\n<p>Automatic checks for the merge request are also carried out at this point. The tools used for this purpose are presented below.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"SwiftLint\"><\/span>SwiftLint<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p class=\"p1\"><span class=\"s1\">On the <a href=\"https:\/\/github.com\/realm\/SwiftLint\">project website<\/a>\u00a0SwiftLint\u00a0is defined as follows:<\/span><\/p>\n<p><em>A tool to enforce Swift style and conventions, loosely based on <a href=\"https:\/\/github.com\/github\/swift-style-guide\">GitHub&#8217;s Swift Style Guide<\/a>.<\/em><\/p>\n<p><em>SwiftLint hooks into <a href=\"http:\/\/clang.llvm.org\/\">Clang<\/a> and <a href=\"http:\/\/www.jpsim.com\/uncovering-sourcekit\">SourceKit<\/a> to use the <a href=\"http:\/\/clang.llvm.org\/docs\/IntroductionToTheClangAST.html\">AST<\/a> representation of your source files for more accurate results.<\/em><\/p>\n<p>SwiftLint is a widely used open source tool with a large code style rule set that checks whether these have been adhered to in the written code. There are numerous additional opt-in rules that can be activated. There is also the possibility to create your own rules (customRules) or to participate in the project itself.<\/p>\n<p class=\"p1\"><span class=\"s1\">The rules are configured in a <code>.swiftlint.yml<\/code> file:<\/span><\/p>\n<pre class=\"lang:yaml decode:true\">analyzer_rules:\r\n\r\n  - unused_declaration\r\n\r\n  - unused_import\r\n\r\ndisabled_rules:\r\n\r\n  - implicit_getter\r\n\r\nopt_in_rules:\r\n\r\n - anyobject_protocol\r\n\r\n  - array_init\r\n\r\n  - attributes\r\n\r\n  - closure_end_indentation\r\n\r\n  ...\r\n\r\n  - yoda_condition\r\n\r\nincluded: # paths to include during linting. `--path` is ignored if present.\r\n\r\n  - app-ios\/Sources\/\r\n\r\n  - app-tvos\/Sources\/\r\n\r\n  - myFramework\/Sources\/\r\n\r\n  - myOtherFramework\/Sources\r\n\r\nexcluded: # paths to ignore during linting. Takes precedence over `included`.\r\n\r\n  - Pods\r\n\r\n# configurable rules can be customized from this configuration file\r\n\r\n# binary rules can set their severity level\r\n\r\nforce_cast: warning # implicitly\r\n\r\nforce_try:\r\n\r\n  severity: warning # explicitly\r\n\r\nidentifier_name:\r\n\r\n  allowed_symbols: \"_\"\r\n\r\n  min_length: 1\r\n\r\n  max_length: 60\r\n\r\n# rules that have both warning and error levels, can set just the warning level\r\n\r\n# implicitly\r\n\r\nline_length: 300\r\n\r\n# they can set both implicitly with an array\r\n\r\ntype_body_length:\r\n\r\n  - 300 # warning\r\n\r\n  - 400 # error\r\n\r\n# or they can set both explicitly\r\n\r\nfile_length:\r\n\r\n  warning: 700\r\n\r\n  error: 1200\r\n\r\n# naming rules can set warnings\/errors for min_length and max_length\r\n\r\n# additionally they can set excluded names\r\n\r\ntype_name:\r\n\r\n  min_length: 4 # only warning\r\n\r\n  max_length: # warning and error\r\n\r\n    warning: 50\r\n\r\n    error: 60\r\n\r\n  excluded:\r\n\r\n    - iPhone\r\n\r\ncustom_rules:\r\n\r\n  disable_print:\r\n\r\n    included: \".*\\\\.swift\"\r\n\r\n    name: \"print usage\"\r\n\r\n    regex: \"((\\\\bprint)|(Swift\\\\.print))\\\\s*\\\\(\"\r\n\r\n    message: \"Prefer mylog over print\"\r\n\r\n    severity: warning\r\n\r\nreporter:\r\n\r\n    - \"xcode\" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji)\r\n\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>If SwiftLint is introduced to an existing project, the number of errors\/warnings can be very high. It makes sense to gradually make parts of the code SwiftLint-conform by configuring included\/excluded directories.<\/p>\n<p>Another help can be <code>swiftlint autocorrect<\/code>\u00a0(<code>swiftlint --fix<\/code> since version 0.43), which automatically fixes easy-to-solve warnings and errors.\u00a0<span class=\"s1\">Typically, the <code>.swiftlint.yml<\/code> file develops in the course of the development and growing codebase of the project and \u201ccustom rules\u201c are introduced.\u00a0To see SwiftLint warnings and errors in Xcode, it can be integrated into Xcode in the\u00a0build phase settings.<\/span><\/p>\n<h2><span class=\"ez-toc-section\" id=\"Danger\"><\/span>Danger<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p class=\"p1\"><span class=\"s1\"><a href=\"https:\/\/danger.systems\">Danger<\/a> is a software originally written in Ruby (implementations are also available in JS, Swift, Kotlin and Python) that runs during the CI process (or standalone). Danger can carry out pre-made or self-developed (custom) code checks for automatic reviews of merge requests that are stored in the\u00a0<code>Dangerfile<\/code> and leave comments in the merge request.\u00a0Typically, this <code>Dangerfile<\/code> develops because the team keeps finding new standards and ideas for automatic checks over time, e.g. to avoid a problem that has occurred before <\/span><span class=\"s1\">(we often added new checks as a measure from the scrum retrospective meeting).\u00a0<\/span><span class=\"s1\">In this way, the team norms can be stored in the <code>Dangerfile<\/code> and the reviewer can concentrate on the \u201chigher\u201c problems when reviewing the merge request.<\/span><\/p>\n<p class=\"p1\"><span class=\"s1\">A comprehensive list of ready-to-use danger plugins (also for many other areas of application such as Android apps) is available on <a href=\"https:\/\/github.com\/danger\/awesome-danger\">GitHub<\/a>.<\/span><\/p>\n<p>Examples of checks in the <code>Dangerfile<\/code>:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/ashfurrow\/danger-ruby-swiftlint\">danger-ruby-swiftlint<\/a>: SwiftLint plugin for Danger. Checks the Swift style.<\/li>\n<li>\n<p class=\"p1\"><span class=\"s1\"><a href=\"https:\/\/github.com\/hanneskaeufler\/danger-todoist\">danger-todoist<\/a>: Warns if a <code>TODO<\/code> is left in the merge request (this potentially indicates that the developer has forgotten something). Our team has decided not to check in any <code>TODO<\/code>s.<\/span><\/p>\n<\/li>\n<li>\n<p class=\"p1\"><span class=\"s2\"><a href=\"https:\/\/github.com\/PGSSoft\/danger-ios_logs\">danger-ios_logs<\/a>: Warns of <code>NSLog<\/code>\u00a0and\u00a0<code>print<\/code> statements in the release code.\u00a0<\/span><\/p>\n<\/li>\n<li><a href=\"https:\/\/github.com\/diogot\/danger-xcode_summary\">danger-xcode_summary<\/a>: A Danger plugin that shows all build errors, warnings and unit tests results generated from <code>xcodebuild<\/code>.<\/li>\n<li><a href=\"https:\/\/github.com\/giginet\/danger-xcprofiler\">danger-xcprofiler<\/a>: If compilation times of each methods are exceeding the thresholds, Danger adds an inline comment to your merge request.<\/li>\n<li><a href=\"https:\/\/github.com\/BrunoMazzo\/Danger-Slather\">Danger-Slather<\/a>: A Danger plugin that shows the code coverage of a Xcode project and file by file using <a href=\"https:\/\/github.com\/SlatherOrg\/slather\">Slather<\/a>. It can add warnings or fail the build if a minimum coverage is not achieved.<\/li>\n<li>\n<p class=\"p1\"><span class=\"s1\">Custom-Check for iPhone and iPad branches in the code: If there is a switch in the code that contains various implementations for iPad and iPhone, a message is displayed that the changes should be tested on the iPad and iPhone.<\/span><\/p>\n<\/li>\n<li>\n<p class=\"p1\"><span class=\"s1\">Custom-Check for changelog modification: Checks whether the changelog file has been modified.\u00a0\u00a0\u200b\u200bUsually a merge request contains\u00a0<\/span><span class=\"s1\">a feature or a bug fix for which there should also be a corresponding changelog entry. This way you can ensure a well-maintained changelog for every release.<\/span><\/p>\n<\/li>\n<li>Custom-Check for <code>info.plist<\/code> modifications: If the <code>info.plist<\/code> has been edited (e.g. the version number has changed) a message with the hint that further <code>info.plist<\/code> files may have to be adapted is displayed (e.g. for <code>NotificationExtensions<\/code>\u00a0or the build scheme of the tvOS app part of the project).<\/li>\n<li>Custom-Check for changes in XIB-files (Xcode Interface Builder files): If a XIB-file has been edited, a message that the developer should add a screenshot of the modified UI to the merge requests displayed.\u00a0Also prevents accidentally edited XIB files from getting into the productions code.<\/li>\n<\/ul>\n<p>Heres an example of a <code>Dangerfile<\/code>:<\/p>\n<pre class=\"lang:ruby decode:true\" title=\"Dangerfile\">require 'json'\r\n\r\n# Check if changelog was edited\r\n\r\nwas_changelog_edited = git.modified_files.any? { |file|\r\n\r\n    File.basename(file) == \"CHANGELOG.yml\"\r\n\r\n}\r\n\r\n# Get ticketnumber, mentioned in MR-Title\r\n\r\nticketnumber = gitlab.mr_title.scan(\/JIRA-[0-9]*\/).first\r\n\r\n# Get Todos in code\r\n\r\ntodos = todoist.todos\r\n\r\n#Swiftlint\r\n\r\nswiftlint.lint_all_files = true\r\n\r\nswiftlint.lint_files(\r\n\r\n    fail_on_error: true,\r\n\r\n    additional_swiftlint_args: '--strict'\r\n\r\n)\r\n\r\n# Print todos unless not empty\r\n\r\nunless todos.empty?\r\n\r\n    message = \"### TODOs found\\n\\n\"\r\n\r\n    message &lt;&lt; \"|File|Line|Text|\\n\"\r\n\r\n    message &lt;&lt; \"| --- | ----- | ----- |\\n\"\r\n\r\n    todos.each { |todo|\r\n\r\n        file_name = File.basename(todo.file)\r\n\r\n        message &lt;&lt; \"|#{file_name}|#{todo.line_number}|#{todo.text}|\\n\"\r\n\r\n    }\r\n\r\n    markdown message\r\n\r\nend\r\n\r\n# Warning for usage of isPad() an isPhone()\r\n\r\ncontainsPadPhoneWarnings = false\r\n\r\nmessage = \"\"\r\n\r\nwarnings = \"\"\r\n\r\nwarn_pad_phone_branches = [\r\n\r\n  { word: 'isPad()', reason: 'New isPad condition \u2013 double-check on both device types.'},\r\n\r\n  { word: 'isPhone()', reason: 'New isPhone condition \u2013 double-check on both device types.'}\r\n\r\n]\r\n\r\nactive_files = (git.modified_files + git.added_files).uniq\r\n\r\nswift_files = active_files\r\n\r\n  .select { |file| file.end_with?('.swift') }\r\n\r\nswift_files.each do |filename|\r\n\r\n  if !File.exist?(filename)\r\n\r\n    next\r\n\r\n  end\r\n\r\n  file = File.read(filename)\r\n\r\n  lines = file.lines\r\n\r\n  diffFile = git.diff_for_file(filename)\r\n\r\n  diffAddedLines = diffFile.patch.scan(\/^\\+(?!\\+|\\+).*$\/)\r\n\r\n  diffText = diffAddedLines.join(\" \")\r\n\r\n  didPrintTableHeader = false\r\n\r\n  lines.each_with_index do |l, index|\r\n\r\n    warn_pad_phone_branches.each do |warn_line|\r\n\r\n      line = lines.index line\r\n\r\n      if l.include? warn_line[:word]\r\n\r\n        if diffText.include? warn_line[:word]\r\n\r\n          containsPadPhoneWarnings = true\r\n\r\n          if !didPrintTableHeader\r\n\r\n            warnings = \"#### :warning: iPad\/iPhone condition found\\n\\n\"\r\n\r\n            warnings &lt;&lt; \"|File|Line|Text|\\n\"\r\n\r\n            warnings &lt;&lt; \"| --- | ----- | ----- |\\n\"\r\n\r\n            didPrintTableHeader = true\r\n\r\n          end\r\n\r\n          warnings &lt;&lt; \"|#{filename}|#{index}|#{warn_line[:reason]}\\n\"\r\n\r\n        end\r\n\r\n      end\r\n\r\n    end\r\n\r\n    warn_compareToFalse_branches.each do |warn_line|\r\n\r\n      line = lines.index line\r\n\r\n      if l.include? warn_line[:word]\r\n\r\n        if diffText.include? warn_line[:word]\r\n\r\n          containsComparesToFalse = true\r\n\r\n          if !didPrintTableHeader\r\n\r\n            warnings = \"#### :warning: someBool == false condition found\\n\\n\"\r\n\r\n            warnings &lt;&lt; \"|File|Line|Text|\\n\"\r\n\r\n            warnings &lt;&lt; \"| --- | ----- | ----- |\\n\"\r\n\r\n            didPrintTableHeader = true\r\n\r\n          end\r\n\r\n          warnings &lt;&lt; \"|#{filename}|#{index}|#{warn_line[:reason]}\\n\"\r\n\r\n        end\r\n\r\n      end\r\n\r\n    end\r\n\r\n  end\r\n\r\nend\r\n\r\n# Check if plist Files were added or changed and if so, ask double-checking the requirement for all other platforms\/flavors\r\n\r\ncontainsInfoPlistChanges = false\r\n\r\nplist_files = (git.modified_files).uniq\r\n\r\n  .select { |file| file.end_with?('.plist') }\r\n\r\nplist_files.each do |filename|\r\n\r\n  if !File.exist?(filename)\r\n\r\n    next\r\n\r\n  end\r\n\r\n  containsInfoPlistChanges = true\r\n\r\nend\r\n\r\n# Print summary\r\n\r\n# 1. Changelog updated\r\n\r\n# 2. Ticket number mentioned in MR-Title\r\n\r\n# 3. No TODOs\/FIXMEs in MR\r\n\r\n# 4. Changed Info.plist\r\n\r\nmessage = \"### Summary:\\n\\n\"\r\n\r\nmessage &lt;&lt; \"|Status|Text|\\n\"\r\n\r\nmessage &lt;&lt; \"| --- | ----- |\\n\"\r\n\r\nmessage &lt;&lt; \"|#{was_changelog_edited ? ':white_check_mark:' : ':x:'}|Changelog updated \\n\"\r\n\r\nmessage &lt;&lt; \"|#{ticketnumber.nil? ? ':x:' : ':white_check_mark:'} | Ticketnumber mentioned in MR-Title \\n\"\r\n\r\nmessage &lt;&lt; \"|#{swiftlint.issues.empty? ? ':white_check_mark:' : ':x:'} | No SwiftLint warnings in MR \\n\"\r\n\r\nmessage &lt;&lt; \"|#{todos.empty? ? ':white_check_mark:' : ':x:'} | No TODOs\/FIXMEs in MR \\n\"\r\n\r\n# Check if xib files were edited\r\n\r\nall_edited_files = git.modified_files + git.added_files\r\n\r\nwas_any_xib_edited = all_edited_files.any? { |file|\r\n\r\n    File.extname(file) == '.xib'\r\n\r\n}\r\n\r\nif (was_any_xib_edited &amp;&amp; !ticketnumber.nil?)\r\n\r\n    ticket_link = \"[#{ticketnumber}](https:\/\/exaring.atlassian.net\/browse\/#{ticketnumber})\"\r\n\r\n    message &lt;&lt; \"|:warning: | Xib files were edited. Consider adding screenshots to #{ticket_link}\\n\"\r\n\r\nend\r\n\r\nif containsPadPhoneWarnings\r\n\r\n    message &lt;&lt; \"|:warning:| `isPad()`\/`isPhone()` conditions were added. Double-check implementation on both device types.\\n\"\r\n\r\nend\r\n\r\nif containsComparesToFalse\r\n\r\n    message &lt;&lt; \"|:warning:| `== false` conditions were added. Double-check implementation and refactor with ! syntax if applicable.\\n\"\r\n\r\nend\r\n\r\nif containsInfoPlistChanges\r\n\r\n    message &lt;&lt; \"|:warning:| Info.plist files have been changed. Double-check the changes to be applied to all product flavors (O2) if required.\\n\"\r\n\r\nend\r\n\r\nmarkdown message\r\n\r\nif (containsPadPhoneWarnings || containsComparesToFalse || containsInfoPlistChanges)\r\n\r\n    markdown \"### Warnings:\\n\\n\"\r\n\r\n    markdown warnings\r\n\r\nend\r\n\r\n# Check for print entries in code\r\n\r\nios_logs.check\r\n\r\n<\/pre>\n<h2><\/h2>\n<h2><span class=\"ez-toc-section\" id=\"Configuration-Bringing-it-all-together\"><\/span>Configuration: Bringing it all together<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p class=\"p1\"><span class=\"s1\">The configuration for performing automatic checks for a merge request\u00a0described below assumes that the project has been automated with Fastlane.\u00a0<\/span><span class=\"s1\">Fastlane is implemented in Ruby. We will use <a href=\"https:\/\/bundler.io\">Bundler<\/a> to run FastLane.\u00a0<\/span><span class=\"s1\">Bundler is a tool that helps to provide a consistent environment for Ruby projects. Bundler reads the so-called <code>Gemfile<\/code> in which the dependencies on <a href=\"https:\/\/guides.rubygems.org\/what-is-a-gem\">Ruby gems<\/a> are defined. This means that a consistent environment can be provided at any time on any system (e.g. on the build server or on the development computer) with the required gems in the appropriate versions.<\/span><\/p>\n<p>Heres an example of a <code>Gemfile<\/code>:<\/p>\n<pre class=\"lang:ruby decode:true\" title=\"Gemfile\">ruby '~&gt; 2.6.3'\r\n\r\nsource 'https:\/\/rubygems.org'\r\n\r\ngem 'cocoapods', '~&gt; 1.9.3'\r\n\r\ngem 'fastlane', '~&gt; 2.167.0'\r\n\r\ngem 'rubyzip'\r\n\r\ngem 'xcode-install'\r\n\r\ngem 'danger-gitlab'\r\n\r\ngem 'danger-swiftlint'\r\n\r\ngem 'danger-todoist'\r\n\r\ngem 'danger-xcode_summary'\r\n\r\ngem 'danger-ios_logs'\r\n\r\nplugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')\r\n\r\neval_gemfile(plugins_path) if File.exist?(plugins_path)\r\n\r\n<\/pre>\n<p>Fastlane comes with its own <a href=\"https:\/\/docs.fastlane.tools\/actions\/danger\/\">action for Danger<\/a>. This action is the entry point and it is executed after creating a merge request in GitLab in the &#8222;test&#8220; stage (<code>bundle exec fastlane run danger<\/code>).\u00a0 To do this, the following configuration must be added in the <code>.gitlab-ci.yml<\/code>:<\/p>\n<pre class=\"lang:default decode:true\">\u2026\r\n\r\ntest:\r\n\r\n  stage: test\r\n\r\n  except:\r\n\r\n    - schedules\r\n\r\n  script:\r\n\r\n    - bundle exec fastlane run danger\r\n\r\n    - bundle exec fastlane test\r\n\r\n  tags:\r\n\r\n    - *IOS\r\n\r\n    - *XC\r\n\r\n  only:\r\n\r\n    refs:\r\n\r\n      - merge_requests\r\n\r\n    changes:\r\n\r\n      - \"**\/*.swift\"\r\n\r\n      - \"**\/*.strings\"\r\n\r\n      - Gemfile.lock\r\n\r\n      - Podfile\r\n\r\n      - Podfile.lock\r\n\r\n      - fastlane\/*\r\n\r\n      - .gitlab-ci.yml\r\n\r\n      - Brewfile\r\n\r\n      - install.sh\r\n\r\n      - \"**\/*.plist\"\r\n\r\n      - \"Dangerfile\"\r\n\r\n  artifacts:\r\n\r\n    paths:\r\n\r\n      - fastlane\/test_output\/\r\n\r\n    expire_in: 3 days\r\n\r\n<\/pre>\n<p>After GitLab has called Danger, it reads its configuration from the <code>Dangerfile<\/code> and carries out the appropriate checks. SwiftLint is also configured as a plug-in in the\u00a0<code>Dangerfile<\/code>. This reads its configuration from the <code>.swiftlint.yml<\/code> file.<\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"In-action-An-example-merge-request\"><\/span>In action: An example merge request<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The merge request on the following screenshot shows Danger and GitLab in action. Danger (and the SwiftLint-Plugin) found a problem with the number of parameters in a function. The other checks were carried out successfully:<\/p>\n<ul>\n<li>The changelog file was adjusted \u2714\ufe0f<\/li>\n<li>Ticket number is included in the title of the merge request \u2714\ufe0f<\/li>\n<li>No <code>TODO<\/code>\/<code>FIXME<\/code> were found in the code\u00a0\u2714\ufe0f<\/li>\n<\/ul>\n<figure id=\"attachment_20684\" aria-describedby=\"caption-attachment-20684\" style=\"width: 1024px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-20684 size-large\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_danger_warning_swiftlint-1024x459.png\" alt=\"Picture: The Danger Swiftlint Plugin found a problem in a merge request\" width=\"1024\" height=\"459\" srcset=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_danger_warning_swiftlint-1024x459.png 1024w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_danger_warning_swiftlint-300x134.png 300w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_danger_warning_swiftlint-768x344.png 768w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_danger_warning_swiftlint-1536x688.png 1536w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_danger_warning_swiftlint-400x179.png 400w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_danger_warning_swiftlint-360x161.png 360w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_danger_warning_swiftlint.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption id=\"caption-attachment-20684\" class=\"wp-caption-text\">A merge request in GitLab that was validated by Danger<\/figcaption><\/figure>\n<h3 class=\"p1\"><span class=\"ez-toc-section\" id=\"What-are-the-advantages\"><\/span><span class=\"s1\">What are the advantages?<\/span><span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li>Direct automated feedback after creating a merge request. New team members automatically learn the norms already set by the team.<\/li>\n<li>Code conventions are checked automatically via \u201ccode\u201c.<\/li>\n<li>Useful automated hints for the reviewer (e.g. &#8222;do an extra check on the iPad&#8220;)<\/li>\n<li>No unnecessary friction in the team due to \u201cindentations&#8220;, &#8222;code style&#8220;, &#8230; &#8211; the machine decides &#8211; the team defines the rules beforehand in the code.<\/li>\n<li>If the team recognizes a certain error pattern, e.g. in the scrum retrospective, a corresponding check can be implemented as a measure.\u00a0In this way it can be avoided that the same error occurs again and again.<\/li>\n<li>The reviewer of the merge request has more time to focus on the core of the merge request.<\/li>\n<\/ul>\n<h2><\/h2>\n<h2><span class=\"ez-toc-section\" id=\"Multiple-Devices-Test\"><\/span>Multiple Devices Test<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>As a mobile developer, we usually test our code on a current device or simulator with the latest operating system version. As a rule, however, older devices with older operating system versions are also supported. In our project there is also the fact that parts of the code are shared with the app for the tvOS platform.\u00a0This can lead to bugs or crashes if, for example, switches for the OS version are implemented incorrectly. Problems can also occur in used 3rd-party libraries that are regularly updated (and potentially contain bugs with every update).<\/p>\n<p>Manual testing with a defined set of device-\/OS-combinations takes a lot of time. For this reason, we have chosen the following way to automatically check the functionality with a defined list of devices &#8211; we call it the Multiple Devices Test.<\/p>\n<p>GitLab offers so-called schedules (comparable to a cron job) with which tasks can be carried out periodically.\u00a0<span class=\"s1\">We have created a schedule that carries out all Unit- and UI-Tests of the project every night on a defined list of device-\/OS-combinations.<\/span><\/p>\n<p>Since this test run takes more than three hours, it would be too time-consuming to execute it within each merge request. The test is carried out via Fastlane. We use the Fastlane action\u00a0<a href=\"https:\/\/docs.fastlane.tools\/actions\/scan\/\">Scan\u00a0<\/a>which offers the possibility of executing tests on a list of devices.\u00a0First we define the list of test devices with which the test should run in the <code>Fastfile<\/code>:<\/p>\n<pre class=\"lang:ruby decode:true \" title=\"Fastfile\">def test_devices(platform)\r\n\r\n  latest_os_version = latest_os_version_for_testing(platform)\r\n\r\n  case platform\r\n\r\n  when \"ios\"\r\n\r\n    [\r\n\r\n      primary_test_device(platform),\r\n\r\n      'iPhone 5s (10.3)',\r\n\r\n      'iPhone 6 (11.4)',\r\n\r\n      'iPad Air 2 (10.3)',\r\n\r\n      \"iPad Pro (10.5-inch) (#{latest_os_version})\",\r\n\r\n      'iPad Pro (9.7-inch) (12.4)',\r\n\r\n    ]\r\n\r\n  when \"tvos\"\r\n\r\n    [\r\n\r\n      primary_test_device(platform)\r\n\r\n    ]\r\n\r\n  else\r\n\r\n    raise \"Invalid platform: #{platform}\"\r\n\r\n  end\r\n\r\nend\r\n\r\n<\/pre>\n<p>Then we create a new lane\u00a0<code>test_nightly_multiple_devices<\/code> in the <code>Fastfile<\/code>:<\/p>\n<pre class=\"lang:ruby decode:true\" title=\"Fastfile\">desc 'Runs all the  tests on multiple iOS\/tvOS devices'\r\n\r\nlane :test_nightly_multiple_devices do |options|\r\n\r\n  prepare_test\r\n\r\n  workspace = 'myproject.xcworkspace'\r\n\r\n  derived_data_path = 'build'\r\n\r\n  # Build once for all test devices (build_for_testing)\r\n\r\n  scan(\r\n\r\n   build_for_testing: true,\r\n\r\n   derived_data_path: derived_data_path,\r\n\r\n   scheme: 'MyProjectTests',\r\n\r\n   configuration: options[:build_scheme_config],\r\n\r\n   slack_only_on_failure: true\r\n\r\n  )\r\n\r\n  # Now, use compiled test and run the unit tests for multiple tvOS devices\r\n\r\n  scan(\r\n\r\n   test_without_building: true,\r\n\r\n   clean: false,\r\n\r\n   derived_data_path: derived_data_path,\r\n\r\n   devices: test_devices('tvos'),\r\n\r\n   max_concurrent_simulators: 1,\r\n\r\n   disable_concurrent_testing: true,\r\n\r\n   scheme: 'MyProjectTests',\r\n\r\n   slack_only_on_failure: true\r\n\r\n  )\r\n\r\n  # Now, use compiled test and run the unit tests for multiple iOS devices\r\n\r\n  scan(\r\n\r\n   test_without_building: true,\r\n\r\n   clean: false,\r\n\r\n   derived_data_path: derived_data_path,\r\n\r\n   devices: test_devices('ios'),\r\n\r\n   max_concurrent_simulators: 1,\r\n\r\n   disable_concurrent_testing: true,\r\n\r\n   scheme: 'MyProjectTests',\r\n\r\n   slack_only_on_failure: true\r\n\r\n  )\r\n\r\n # Now, use compiled test and run the UI Tests for multiple iOS devices\r\n\r\n  scan(\r\n\r\n    devices: test_devices('ios'),\r\n\r\n    max_concurrent_simulators: 1,\r\n\r\n    disable_concurrent_testing: true,\r\n\r\n    scheme: 'MyProjectUITests',\r\n\r\n    clean: false,\r\n\r\n    slack_only_on_failure: true\r\n\r\n  )\r\n\r\nend\r\n\r\n<\/pre>\n<p>Now we add the section\u00a0<code>test_nightly_multiple_devices<\/code> to the <code>.gitlab-ci.yml<\/code>.\u00a0 This allows the multiple devices test to be started via the GitLab schedule.<\/p>\n<pre class=\"lang:default decode:true\" title=\".gitlab-ci.yml\">\u2026\r\n\r\ntest_nightly_multiple_devices:\r\n\r\n  stage: test\r\n\r\n  only:\r\n\r\n    refs:\r\n\r\n      - schedules\r\n\r\n    variables:\r\n\r\n      - $NIGHTLY_BUILD_SCHEME == \"Debug\"\r\n\r\n  script:\r\n\r\n    - bundle exec fastlane test_nightly_multiple_devices build_scheme_config:${NIGHTLY_BUILD_SCHEME}\r\n\r\n  tags:\r\n\r\n    - *TVOS\r\n\r\n    - *XC\r\n\r\n  artifacts:\r\n\r\n    paths:\r\n\r\n      - fastlane\/test_output\/\r\n\r\n    expire_in: 3 days\r\n\r\n\u2026\r\n\r\n<\/pre>\n<p>Now the corresponding schedule can then be created in Gitlab:<\/p>\n<figure id=\"attachment_20688\" aria-describedby=\"caption-attachment-20688\" style=\"width: 1024px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-20688 size-large\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_schedules_overview-1024x438.png\" alt=\"Picture: GitLab Schedules Overview\" width=\"1024\" height=\"438\" srcset=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_schedules_overview-1024x438.png 1024w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_schedules_overview-300x128.png 300w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_schedules_overview-768x329.png 768w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_schedules_overview-1536x658.png 1536w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_schedules_overview-400x171.png 400w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_schedules_overview-360x154.png 360w, https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/gitlab_schedules_overview.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption id=\"caption-attachment-20688\" class=\"wp-caption-text\">GitLab Schedules Overview<\/figcaption><\/figure>\n<p>All tests are now regularly carried out on the defined device-\/OS-combinations and, in the event of an error, a message is posted via e-mail and in the slack team channel.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Advantages-of-the-Multiple-Devices-Test\"><\/span>Advantages of the Multiple Devices Test<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li>Automatic check that the code does not crash on the defined platforms \u2013 no more: \u201cOops, the app crashes on iOS 11 immediately after starting\u201c<\/li>\n<li>Problems with the UI on older OS versions can be recognized by the fact that the UI-Tests are also run on these OS versions<\/li>\n<li>Any problems with 3rd-party libraries on older operating system versions can be discovered<\/li>\n<li>Shared iOS\/tvOS code\u00a0is being tested on both platforms<\/li>\n<\/ul>\n<p>The multiple device test helped us to identify bugs and problems before the release. The implementation effort is low and gives the team an additional insurance to deliver the best possible release.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Thats-it\"><\/span>That&#8217;s it ?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Thanks for reading! Maybe the measures described will find their way into the development process of your project.<\/p>\n<p>Are you looking for support in your app development process? Have a look at <a href=\"https:\/\/www.inovex.de\/en\/our-services\/apps\/\">our offerings<\/a>!<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Are you working in a development team on an extensive and dynamic iOS project? Then Fastlane, Danger, SwiftLint and GitLab can help you to ensure quality and save time.<\/p>\n","protected":false},"author":207,"featured_media":20856,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[396,397,398,399,400,401],"service":[420],"coauthors":[{"id":207,"display_name":"Jochen Holzer","user_nicename":"jholzer"}],"class_list":["post-20619","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-danger","tag-gitlab","tag-ios","tag-swift","tag-swiftlint","tag-tvos","service-apps"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Automatic quality assurance measures in iOS\/tvOS projects with Fastlane, Danger and SwiftLint<\/title>\n<meta name=\"description\" content=\"This article shows how Fastlane, Danger, SwiftLint and GitLab can help to ensure quality and save time when working in a development team on an extensive and dynamic iOS project.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Automatic quality assurance measures in iOS\/tvOS projects with Fastlane, Danger and SwiftLint\" \/>\n<meta property=\"og:description\" content=\"This article shows how Fastlane, Danger, SwiftLint and GitLab can help to ensure quality and save time when working in a development team on an extensive and dynamic iOS project.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2021-02-18T19:00:04+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-12-01T11:25:13+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Jochen Holzer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie-1024x576.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jochen Holzer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"17\u00a0Minuten\" \/>\n\t<meta name=\"twitter:label3\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data3\" content=\"Jochen Holzer\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/\"},\"author\":{\"name\":\"Jochen Holzer\",\"@id\":\"https:\/\/www.inovex.de\/de\/#\/schema\/person\/9e564dc3b945c60a58d9980bce5d8600\"},\"headline\":\"Automatic Quality Assurance Measures in an iOS\/tvOS Project with Fastlane, Danger and SwiftLint\",\"datePublished\":\"2021-02-18T19:00:04+00:00\",\"dateModified\":\"2022-12-01T11:25:13+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/\"},\"wordCount\":2106,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.inovex.de\/de\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png\",\"keywords\":[\"Danger\",\"GitLab\",\"iOS\",\"Swift\",\"SwiftLint\",\"tvOS\"],\"articleSection\":[\"Applications\",\"English Content\",\"General\",\"Methods\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/\",\"url\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/\",\"name\":\"Automatic quality assurance measures in iOS\/tvOS projects with Fastlane, Danger and SwiftLint\",\"isPartOf\":{\"@id\":\"https:\/\/www.inovex.de\/de\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png\",\"datePublished\":\"2021-02-18T19:00:04+00:00\",\"dateModified\":\"2022-12-01T11:25:13+00:00\",\"description\":\"This article shows how Fastlane, Danger, SwiftLint and GitLab can help to ensure quality and save time when working in a development team on an extensive and dynamic iOS project.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#primaryimage\",\"url\":\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png\",\"contentUrl\":\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png\",\"width\":1920,\"height\":1080,\"caption\":\"A pipe system with technology logos\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.inovex.de\/de\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Automatic Quality Assurance Measures in an iOS\/tvOS Project with Fastlane, Danger and SwiftLint\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.inovex.de\/de\/#website\",\"url\":\"https:\/\/www.inovex.de\/de\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/www.inovex.de\/de\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.inovex.de\/de\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.inovex.de\/de\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\/\/www.inovex.de\/de\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/inovexde\",\"https:\/\/x.com\/inovexgmbh\",\"https:\/\/www.instagram.com\/inovexlife\/\",\"https:\/\/www.linkedin.com\/company\/inovex\",\"https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.inovex.de\/de\/#\/schema\/person\/9e564dc3b945c60a58d9980bce5d8600\",\"name\":\"Jochen Holzer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/www.inovex.de\/de\/#\/schema\/person\/image\/8bd00fa348fe1d13ed07ecf86523c5b9\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/9548e16ca468a395814647ad2ee4e277c9a92f5ec6bdaebc5a41c92b9d14ad23?s=96&d=retro&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/9548e16ca468a395814647ad2ee4e277c9a92f5ec6bdaebc5a41c92b9d14ad23?s=96&d=retro&r=g\",\"caption\":\"Jochen Holzer\"},\"sameAs\":[\"http:\/\/jochen-holzer.de\/\",\"https:\/\/www.linkedin.com\/in\/jochen-holzer\"],\"url\":\"https:\/\/www.inovex.de\/de\/blog\/author\/jholzer\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Automatic quality assurance measures in iOS\/tvOS projects with Fastlane, Danger and SwiftLint","description":"This article shows how Fastlane, Danger, SwiftLint and GitLab can help to ensure quality and save time when working in a development team on an extensive and dynamic iOS project.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/","og_locale":"de_DE","og_type":"article","og_title":"Automatic quality assurance measures in iOS\/tvOS projects with Fastlane, Danger and SwiftLint","og_description":"This article shows how Fastlane, Danger, SwiftLint and GitLab can help to ensure quality and save time when working in a development team on an extensive and dynamic iOS project.","og_url":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2021-02-18T19:00:04+00:00","article_modified_time":"2022-12-01T11:25:13+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png","type":"image\/png"}],"author":"Jochen Holzer","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie-1024x576.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Verfasst von":"Jochen Holzer","Gesch\u00e4tzte Lesezeit":"17\u00a0Minuten","Written by":"Jochen Holzer"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/"},"author":{"name":"Jochen Holzer","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/9e564dc3b945c60a58d9980bce5d8600"},"headline":"Automatic Quality Assurance Measures in an iOS\/tvOS Project with Fastlane, Danger and SwiftLint","datePublished":"2021-02-18T19:00:04+00:00","dateModified":"2022-12-01T11:25:13+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/"},"wordCount":2106,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png","keywords":["Danger","GitLab","iOS","Swift","SwiftLint","tvOS"],"articleSection":["Applications","English Content","General","Methods"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/","url":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/","name":"Automatic quality assurance measures in iOS\/tvOS projects with Fastlane, Danger and SwiftLint","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png","datePublished":"2021-02-18T19:00:04+00:00","dateModified":"2022-12-01T11:25:13+00:00","description":"This article shows how Fastlane, Danger, SwiftLint and GitLab can help to ensure quality and save time when working in a development team on an extensive and dynamic iOS project.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/02\/Continuous-Delivery-von-iOS-Apps-Kopie.png","width":1920,"height":1080,"caption":"A pipe system with technology logos"},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/de\/blog\/automatic-quality-assurance-measures-ios-tvos-fastlane-danger-swiftlint\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/de\/"},{"@type":"ListItem","position":2,"name":"Automatic Quality Assurance Measures in an iOS\/tvOS Project with Fastlane, Danger and SwiftLint"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/de\/#website","url":"https:\/\/www.inovex.de\/de\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/de\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/9e564dc3b945c60a58d9980bce5d8600","name":"Jochen Holzer","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/image\/8bd00fa348fe1d13ed07ecf86523c5b9","url":"https:\/\/secure.gravatar.com\/avatar\/9548e16ca468a395814647ad2ee4e277c9a92f5ec6bdaebc5a41c92b9d14ad23?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/9548e16ca468a395814647ad2ee4e277c9a92f5ec6bdaebc5a41c92b9d14ad23?s=96&d=retro&r=g","caption":"Jochen Holzer"},"sameAs":["http:\/\/jochen-holzer.de\/","https:\/\/www.linkedin.com\/in\/jochen-holzer"],"url":"https:\/\/www.inovex.de\/de\/blog\/author\/jholzer\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/20619","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/users\/207"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/comments?post=20619"}],"version-history":[{"count":1,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/20619\/revisions"}],"predecessor-version":[{"id":39764,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/20619\/revisions\/39764"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media\/20856"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media?parent=20619"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/tags?post=20619"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/service?post=20619"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/coauthors?post=20619"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}