/xamarin-continuous-integration

Xamarin Continuous Integration with Unit Tests & Acceptance (UI) Tests

Primary LanguageC#MIT LicenseMIT

Xamarin - Continuous Integration with Unit & Acceptance (UI) Tests

Description

Xamarin - Continuous Integration with Unit & Acceptance (UI) Tests is a sample Xamarin (iOS & Android) application built to highlight the infrastructure for running unit tests and Acceptance (UI) Tests using Jenkins.

The infrastructure is built around the default VS for Mac Xamarin Template Application.

Note: The main purpose of this repository is to highlight the infrastructure surrounding automated Unit & Acceptance (UI) Tests and NOT on Unit Testing and Acceptance (UI) Testing design patterns (maybe just slightly on Specflow & Page Object Pattern integration). Please see the Useful Resources section for Unit Testing and Acceptance (UI) Testing design tips and tricks.

All logic found in the .sh scripts can also be found in the .cake files used by Cake Build. Feel free to go with either bash scripts or .cake scripts on your own projects - you don't have to use both approaches. We suggest having a look at the .sh files first to understand what happens under the hood and after that you can migrate to the more maintainable .cake scripts.

Requirements

High Level CI Pipeline

High level UML diagram

  • Pull Request Job
    • Is triggered whenever creating a new Pull Request
    • Runs Scripts/healthcheck.sh that does the following:
      • Builds all projects (Jenkins.iOS, Jenkins.Droid, Jenkins.UnitTests, Jenkins.UITests)
      • Using StyleCop.MsBuild referred by each project individually it outputs a obj/Debug/StyleCopViolations.xml report file for each project
      • Runs unit tests using NUnit.ConsoleRunner (the unit tests are written only for the shared project) - the test report can be found in the solution root folder in TestResult.xml
      • If you would want to run unit tests for the specific iOS and Android projects those tests would need to run on simulators / emulators / actual devices. (usually unit testing just the shared project should suffice)
    • Fails the build if:
      • Any of the projects fails building
      • Any unit test failed
      • StyleCop warnings are introduced (doing a delta with the main branch)
    • Adds a Success / Fail comment to the Git Server (e.g. Stash/BitBucket Server)
  • Health Check Job
    • Is triggered whenever merging a Pull Request or doing a direct Commit & Push on the main branch
    • Runs Scripts/healthcheck.sh that does the following:
      • Builds all projects (Jenkins.iOS, Jenkins.Droid, Jenkins.UnitTests, Jenkins.UITests)
      • Using StyleCop.MsBuild referred by each project individually it outputs a obj/Debug/StyleCopViolations.xml report file for each project
      • Runs unit tests using NUnit.ConsoleRunner (the unit tests are written only for the shared project) - the test report can be found in the solution root folder in TestResult.xml
      • If you would want to run unit tests for the specific iOS and Android projects those tests would need to run on simulators / emulators / actual devices. (usually unit testing just the shared project should suffice)
    • Fails the build if:
      • Any of the projects fails building
      • Any unit test failed
      • StyleCop warnings are introduced (doing a delta with the main branch)
    • Sends an email notification to the interested parties if the build fails
    • Triggers iOS and Android jobs on success
  • iOS Job
    • Is triggered after each successful Health Check Job build
    • Runs iOS/Scripts/build_ipa.sh that does the following:
      • Builds Release configuration .ipa (depending on your backend environments you might have several other configurations that you'd want to build)
    • Runs iOS/Scripts/build_app.sh that does the following:
      • Builds the Debug configuration .app that is used for running the UI Tests. It is important that whatever configuration we choose to build (besides the Release one) it should link the Xamarin.TestCloud.Agent library and call Xamarin.Calabash.Start(); in AppDelegate.cs taking into account the ENABLE_TEST_CLOUD flag which should not be used for the Release configuration
      • It is important that the Debug|iPhoneSimulator builds a single architecture (such as x86_x64) as you cannot install FAT binaries on the simulator
    • Integrates manual Promotion mechanisms for automatic store / beta distribution (based on the green light given by the automated Acceptance (UI) Tests, QA Team and Stakeholders)
    • Sends an email notification to the interested parties if the build fails
  • Android Job
    • Is triggered after each successful Health Check Job build
    • Runs Droid/Scripts/build_apk.sh that does the following:
      • Builds Release configuration .apk - that is aligned and signed using Droid/Scripts/ProductionKey.keystore (depending on your backend environments you might have several other configurations that you'd want to build)
      • It is important that the Release configuration contains emulator architectures as well (such as x86_x64 - which by default it doesn't) so the .apk can be installed on the emulator
      • Also you should consider keeping the Keystore Password in a place outside your codebase
    • Integrates manual Promotion mechanisms for automatic store / beta distribution (based on the green light given by the automated Acceptance (UI) Tests, QA Team and Stakeholders)
    • Sends an email notification to the interested parties if the build fails
  • iOS UI Tests Job
    • Is triggered nightly
    • It runs the UI Tests on the .app artifact created by the latest successful iOS Job build
    • Runs UITests/Scripts/run_ios_tests.sh that does the following:
      • It creates (if it doesn't already exist) an iOS Simulator based on the passed name, device type and iOS runtime parameters (disabling the keyboard settings for fixing text entry issues)
      • It runs the UI automated tests by updating the resource files found in the Settings folder with iOS specific settings
      • It is important that the desired simulator runtimes are already installed on the machine
    • Consumes the .json reports using the Cucumber Reports Plugin that can be found in UITests/bin/debug
    • Fails the build if any of the tests failed (sending an email to the interested parties)
    • Improvement opportunities:
      • Can be (fairly easily) updated to run the tests in parallel on multiple simulators
      • Can be (failry easily) updated to run the tests alternatively (based of the Jenkins job build number) on different iOS Simulator versions
  • Android UI Tests Job
    • Is triggered nightly
    • It runs the UI Tests on the .apk artifact created by the latest successful Android Job build
    • Runs UITests/Scripts/run_android_tests.sh that does the following:
      • It creates (overriding if already exists) and Android Emulator based on the passed name, and API level parameters (disabling the software keyboard for easy text entry)
      • It runs the UI automated tests by updating the resource files found in the Settings folder with Android specific settings
      • It is important that the desired emulator system-images are already installed on the machine
    • Consumes the .json reports using the Cucumber Reports Plugin that can be found in UITests/bin/debug
    • Fails the build if any of the tests failed (sending an email to the interested parties)
    • Improvement opportunities:
      • Can be (fairly easily) updated to run the tests in parallel on multiple emulators
      • Can be (failry easily) updated to run the tests alternatively (based of the Jenkins job build number) on different Android emulator versions

FAQ

Q: Why bundling everything in script instead of havin the content of the scripts directly in our Jenkins jobs?

A: Generally it is a good practice that all your build / configuration scripts are kept alongside your codebase. This make it easy to run the scripts on any machine (independently of your CI server) and it also helps tracking any changes (and their reason). Build / configuration scripts should be first class citizens.

Q: What about Jenkins jobs? Shouldn't they be scripted as well?

A: Yes, they should. Pipeline is a good way to achieve that. However, you want to keep your CI specific scripts to a minimum, in order to easily change it if needed. For the purpose of this sample project we didn't include the actual Jenkins job config.xml / Jenkinsfiles but we hope that the High Level CI Pipeline section does a good job explaining how they should be configured.

Q: Why did you use the Resources (Settings folder) for UITests project?

A: We wanted to find a mechanism that allows easy IDE & Command Line integration. The idea was the following - if you want to use the IDE you leave the AndroidSettings.resx and the IosSettings.resx empty. If you want to use the Command Line (by running the tests via Jenkins) you should insert the corresponding values in the mentioned files. For choosing on what platform you want to run the tests (iOS or Android) you should change the GlobalSettings.resx file regardless of using the IDE or the Command Line

Q: Why do all my Steps.cs classes inherit from ReportingStepDefinitions?

A: Unfortunately Specflow free version sucks at reporting (compared for instance with the Java implementation of Cucumber). By using SpecNuts.Json you can generate the same .json style reports as Cucumber-JVM that can be easily integrated in Jenkins using the Cucumber Reports Plugin. Unfortunately we haven't found a way to embedd screenshots yet using the free version of Specflow - when we'll find one, we'll update this repo.

Q: Why does the Jenkins.UITest project use a different NUnit version than Jenkins.UnitTests

A: The Straight8's SpecFlow Integration extension generates the glue .cs code using and older version of NUnit - making it incompatible with the newer versions.

Q: Why didn't you script out the Android SDK / system images and iOS Simulator installation as well?

A: Ideally we should have done that, as running the tests should work on any machine without any manual intervention. Feel free to do that on your own projects - for the purpose of this example it would've been a bit too much effort.

Q: Why did you bundled in the codebase the file UITests/Scripts/config.ini?

A: Is a convenience placeholder for easily creating a new Android Emulator. It might have to be kept up to date as newer Android SDK versions are released.

Q: Can I use Xamarin.UITests even if my applications are built using native Java/Kotlin or Swift/Objective-C?

A: Yes - you write your UI Tests using Xamarin while the applications are written using native Java/Kotlin or Swift/Objective-C. The only trick is that you'll need to link the Calabash.framework in your iOS Debug application.

Useful Resources

Contributing

Please feel free to open an issue for any questions or suggestions you have!