Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DevOps sur Android : D'un git push à une release Play Store

DevOps sur Android : D'un git push à une release Play Store

- Devoxx France 2016
- Breizhcamp 2016
- Paris Android User Group Mars 2016

05dd369062f7c4e450e1e08d1471da5b?s=128

Jeremie Martinez

March 25, 2016
Tweet

Transcript

  1. DevOps sur Android D'un git push à une release sur

    le Play Store @JeremMartinez www.jeremie-martinez.com
  2. Nous recrutons !

  3. Les processes dépendent du contexte

  4. DevOps n.pr [dɛvops] Fluidifier le processus de mise en production

    en rassemblant les équipes de développement et les équipes opérationnelles. Play Store
  5. Intégration continue Livraison continue Déploiement continu Tests unitaires Automatisation Release

    Packaging Build Tests d’intégration Test d’instrumentation Quality
  6. Intégration continue Build automatique Fail fast Testé constamment Packaging constant

    Release plus facile
  7. Base d’un process solide

  8. Packaging Quality Tests Build Push

  9. Push dev master v19 v20

  10. Push dev master v19 v20 feature

  11. Build Push Packaging Quality Tests

  12. Build 2 builds: debug et release

  13. Build

  14. Packaging Quality Tests Build Push

  15. Tests Oui, même sur Android

  16. Tests unitaires Valider des fonctionnalités Garantir des comportements Empêcher les

    regressions
  17. JUnit4 public final class LordOfTheRingsTest {
 
 @Before
 public void

    setup() { … }
 
 @Test
 public void shouldBringThePrecious() { … } @After
 public void tearDown() { … } } Tests unitaires
  18. Robolectric public final class LordOfTheRingsTest {
 
 @Before
 public void

    setup() { … }
 
 @Test
 public void shouldBringThePrecious() { … } @After
 public void tearDown() { … } } Tests unitaires
  19. @RunWith(RobolectricTestRunner.class)
 @Config(manifest = Config.NONE) Tests unitaires Robolectric public final class

    LordOfTheRingsTest {
 
 @Before
 public void setup() { … }
 
 @Test
 public void shouldBringThePrecious() { … } @After
 public void tearDown() { … } }
  20. @Test
 public void shouldRestoreFromParcelable() { // Given Character item =

    new Character("Frodo", "Baggins", "Hobbit"); 
 // When
 Parcel parcel = Parcel.obtain();
 item.writeToParcel(parcel, 0);
 parcel.setDataPosition(0); // Then Character fromParcel = Character.CREATOR.createFromParcel(parcel); assertThat(fromParcel.firstname).isEqualTo("Frodo"); assertThat(fromParcel.lastname).isEqualTo("Baggins");
 assertThat(fromParcel.race).isEqualTo("Hobbit"); } Tests unitaires
  21. Ne testez pas Android Tests unitaires

  22. Utilisez un framework d’assertions Truth ou AssertJ par exemple Tests

    unitaires
  23. Tests d’intégration Espresso ou Robotium

  24. Tests d’intégration Espresso ou Robotium

  25. Tests d’intégration Testez uniquement les user-flows principaux

  26. Tests d’intégration Recherche Panier Paiement Billets Exemple pour Captain Train

  27. Tests

  28. Quality Tests Build Push Packaging

  29. Quality

  30. Quality Lint

  31. Quality Lint Custom

  32. Quality public class AttrPrefixDetector extends ResourceXmlDetector {
 
 public static

    final Issue ISSUE = Issue.create("AttrNotPrefixed", //
 "You must prefix your custom attr by `ct`", //
 "We prefix all our attrs to avoid clashes.", //
 Category.TYPOGRAPHY, //
 5, // Priority
 Severity.WARNING, //
 new Implementation(AttrPrefixDetector.class, // Scope.RESOURCE_FILE_SCOPE) // );
  33. Quality // Only XML files
 @Override
 public boolean appliesTo(@NonNull Context

    context,
 @NonNull File file) {
 return LintUtils.isXmlFile(file);
 }
  34. Quality // Only values folder
 @Override
 public boolean appliesTo(ResourceFolderType folderType)

    {
 return ResourceFolderType.VALUES == folderType;
 }
  35. Quality // Only attr tag
 @Override
 public Collection<String> getApplicableElements() {


    return Collections.singletonList(TAG_ATTR);
 }
  36. Quality // Only name attribute
 @Override
 public Collection<String> getApplicableAttributes() {


    return Collections.singletonList(ATTR_NAME);
 }
  37. Quality @Override
 public void visitElement(XmlContext context, Element element) {
 final

    Attr attributeNode = element.getAttributeNode(ATTR_NAME);
 if (attributeNode != null) {
 final String val = attributeNode.getValue();
 if (!val.startsWith("android:") && !val.startsWith("ct")) {
 context.report(ISSUE,
 attributeNode,
 context.getLocation(attributeNode),
 "You must prefix your custom attr by `ct`");
 }
 }
 }
  38. Quality public final class CaptainRegistry extends IssueRegistry {
 @Override
 public

    List<Issue> getIssues() {
 return Collections.singletonList(AttrPrefixDetector.ISSUE);
 }
 }
  39. Quality

  40. Quality Ne sous-estimez pas les règles Lint

  41. Packaging Quality Tests Build Push

  42. Packaging Filtrez vos ressources Proguard Assemble Signez votre APK Zipalign

    votre APK zipalign -z 4 in.apk out.apk
  43. Packaging Quality Tests Build Push Intégration continue

  44. Packaging Quality Tests Build Push Intégration continue

  45. Intégration continue

  46. Automatiser les livraisons afin de pouvoir livrer rapidement et facilement

    Intégration continue Livraison continue
  47. Déploiement continu

  48. « Trop de mises à jour »

  49. Pas assez de contenu

  50. Pouvoir ≠ Vouloir

  51. Une release, c’est quoi ?

  52. Beta Rollout 5% Rollout 50% Release 100% 1 semaine 1

    semaine
  53. Beta Rollout 5% Rollout 50% Release 100% Toutes les 6

    semaines Le mardi
  54. Intégration continue Livraison continue dev master v21

  55. Publish Screenshots Livraison continue

  56. Screenshots Ne négligez pas les screenshots

  57. Screenshots Automatisez les screenshots

  58. Outil maison avec UiAutomator Spoon par Square Screengrab par Fastlane

    Screenshots
  59. Publish Livraison continue Screenshots

  60. Éviter les surprises Publish

  61. Vérifiez vos permissions Publish

  62. private static final String[] EXPECTED_PERMISSIONS = { … } @Test


    public void shouldMatchPermissions() throws Exception {
 final AndroidManifest androidManifest = new AndroidManifest( //
 Fs.fileFromPath("build/intermediates/manifests/full/debug/AndroidManifest.xml"), //
 null, //
 null //
 );
 final Set<String> requestedPermissions = new HashSet<>(androidManifest.getUsedPermissions());
 
 assertThat(requestedPermissions).containsOnly(EXPECTED_PERMISSIONS);
 } Publish
  63. Testez vos upgrades de database Publish

  64. testCompile 'org.xerial:sqlite-jdbc:3.8.10.1'
 Publish

  65. @Test
 public void upgradeShouldBeTheSameAsCreate() throws Exception {
 DbOpenHelper helper =

    new DbOpenHelper(RuntimeEnvironment.application);
 
 helper.onCreate(newDatabase());
 helper.onUpgrade(originDatabase(), 1, CURRENT_VERSION);
 
 Set<String> newSchema = extractSchema(newFile.getAbsolutePath());
 Set<String> upgradedSchema = extractSchema(upgradedFile.getAbsolutePath());
 
 assertThat(upgradedSchema).isEqualTo(newSchema);
 } Publish
  66. connection = DriverManager.getConnection(JDBC_SQLITE + url);
 
 tables = connection.getMetaData().getTables(null, null,

    null, null);
 while (tables.next()) {
 
 final String tableType = tables.getString("TABLE_TYPE");
 final String tableName = tables.getString("TABLE_NAME");
 schema.add(tableType + " " + tableName);
 Publish
  67. columns = connection.getMetaData().getColumns(null, null, tableName, null);
 while (columns.next()) {
 


    final String columnName = columns.getString("COLUMN_NAME");
 final String columnType = columns.getString("TYPE_NAME");
 final String columnNullable = columns.getString("IS_NULLABLE");
 final String columnDefault = columns.getString("COLUMN_DF");
 schema.add("TABLE " + tableName +
 " COLUMN " + columnName + " " + columnType +
 " NULLABLE=" + columnNullable +
 " DEFAULT=" + columnDefault);
 } Publish
  68. Tables / Views Columns Primary Keys Cross-references Indexes Publish

  69. None
  70. Publish

  71. Publish

  72. Publish

  73. Choisissez votre client HTTP - Java - Ruby - Python

    Publish
  74. compile 'com.google.apis:google-api-services-androidpublisher:v2-rev20-1.21.0'
 Publish

  75. http = GoogleNetHttpTransport.newTrustedTransport();
 json = JacksonFactory.getDefaultInstance();
 
 GoogleCredential credential =

    new GoogleCredential.Builder().
 setTransport(http).
 setJsonFactory(json).
 setServiceAccountPrivateKeyId(KEY_ID).
 setServiceAccountId(SERVICE_ACCOUNT_EMAIL).
 setServiceAccountScopes(AndroidPublisherScopes.ANDROIDPUBLISHER).
 setServiceAccountPrivateKeyFromPemFile(secretFile).
 build();
 
 publisher = new AndroidPublisher.Builder(http, json, credential).
 setApplicationName(PACKAGE).
 build(); Publish
  76. AndroidPublisher.Edits edits = publisher.edits();
 
 AppEdit edit = edits.insert(PACKAGE, null).execute();


    String id = edit.getId(); Publish
  77. Listings listings = edits.listings(); Listing listing = new Listing().
 setFullDescription(description).


    setShortDescription(shortDescription).
 setTitle(title);
 
 listings.update(PACKAGE, id, "en_US", listing).execute(); Publish
  78. Images images = edits.images();
 
 FileContent content = new FileContent(PNG_MIME_TYPE,

    file);
 
 images.upload(PACKAGE, id, "en_US", "phone5", content).execute(); Publish "tablet7" "tablet9"
 "wear"
  79. 
 Apks apks = edits.apks(); 
 FileContent apkContent = new

    FileContent(APK_MIME_TYPE, apkFile);
 Apk apk = apks.upload(PACKAGE, id, apkContent).execute(); 
 int version = apk.getVersionCode();
 Publish
  80. 
 // Assign APK to Track
 Tracks tracks = edits.tracks();

    
 Track track = new Track().setVersionCodes(version); 
 tracks.update(PACKAGE, id, "production", track).execute(); Publish "rollout" "beta" "alpha"
  81. 
 // Update APK listing
 Apklistings apklistings = edits.apklistings(); 


    ApkListing whatsnew = new ApkListing().setRecentChanges(changes); 
 apklistings.update(PACKAGE, id, version, "en_US", whatsnew).execute(); Publish
  82. edits.validate(PACKAGE, id).execute(); 
 edits.commit(PACKAGE, id).execute(); Publish

  83. Listings Screenshots APK Nouveautés Track Publish

  84. Outil maison Plugin Gradle Plugin Jenkins Publish

  85. Screenshots Livraison continue Publish

  86. Publish Screenshots Livraison continue Packaging Quality Tests Build Push Intégration

    continue
  87. Questions ? @JeremMartinez www.jeremie-martinez.com