Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

fail fast

Slide 3

Slide 3 text

Anyone who has never made a mistake has never tried anything new. -Albert Einstein

Slide 4

Slide 4 text

IT BIZ ↔ Me

Slide 5

Slide 5 text

Fail fast, Fail often, Fail better, Succeed

Slide 6

Slide 6 text

Ship fast, Iterate often, Don’t crash, Succeed Fail fast, Fail often, Fail better

Slide 7

Slide 7 text

# KPIs

Slide 8

Slide 8 text

Crash-free users >99.5% Release once a week

Slide 9

Slide 9 text

New app once a week???

Slide 10

Slide 10 text

Release Once a month x1 x2 x5 x6 x3 x4 x7 x8 team’s capacity

Slide 11

Slide 11 text

x1 x2 x3 x4 x5 x6 x7 x8 build 1 checking Dev QA/Business build1 build2 Week 1 Week 2 Week 3 “W hat’s wrong, W hat’s ok?” “W hat’s wrong, W hat’s ok?” build 2 checking

Slide 12

Slide 12 text

x1 x2 x3 x4 x5 x6 x7 x8 build 1 testing x1’ x2’ x4’ build 2 testing Dev QA/Business build1 build2 Week 1 Week 2 Week 3 “we don’t like x5, x8” “we don’t like x1, x2, x4” x5’ x8’ Week 4

Slide 13

Slide 13 text

x1 x2 x3 x4 x5 x6 x7 x8 build 1 testing x1’ x2’ x4’ build 2 testing Dev QA/Business build1 build2 build3 Week 1 Week 2 Week 3 fixes x5’ x8’ x1’ x2’ x4' Week 4 more fixes “checking them now..” x5’ x8’ RELEASE!!!

Slide 14

Slide 14 text

x1 x2 y2 z1 x3 y1 z2 z3 team’s capacity (same as previous) Release Once a week

Slide 15

Slide 15 text

Week 1 x1 x2 x1’ x2’ x1 x2 x1’ Dev QA/Business

Slide 16

Slide 16 text

x1’ x2’ x1’ build 1 build 2 Week 1 x1 x2 x1 x2 Dev QA/Business

Slide 17

Slide 17 text

x1’ feedback 1 feedback 2 Week 1 x1 x2 x1’ x2’ x1 x2 Dev QA/Business

Slide 18

Slide 18 text

Week 1 x1 x2 x1’ x2’ Week 2 x3 x1 x2 x1’ Dev QA/Business x2’ x3 build 4 build 5 release candidate v1 RELEASE!

Slide 19

Slide 19 text

Week 1 x1 x2 x1’ x2’ Week 2 x3 y1 y2 x1 x2 x1’ Dev QA/Business x2’ y1 x3 feedback 1 build Week 3

Slide 20

Slide 20 text

Week 1 x1 x2 x1’ x2’ Week 2 x3 y1 y2 x1 x2 x1’ Dev QA/Business y1’ z1 z2 Week 3 x2’ y1 y1’ z1 y2 x3 v2 RELEASE!

Slide 21

Slide 21 text

Week 1 x1 x2 x1’ x2’ Week 2 x3 y1 y2 x1 x2 x1’ Dev QA/Business y1’ z1 z2 Week 3 x2’ y1 y1’ z1 y2 x3 z3 v3 RELEASE! Week 4 z1’ z2’ z3 z1’ z2’ z2

Slide 22

Slide 22 text

x1 x2 y2 z1 x3 y1 z2 z3 x1 x2 x5 x6 x3 x4 x7 x8 vs

Slide 23

Slide 23 text

x1 x2 y2 z1 x3 y1 z2 z3 x1 x2 x5 x6 x3 x4 x7 x8 • Same context, reasonable overhead • Different context, a lot to keep in mind How many things to have in mind by engineers Software 
 engineer focus QA 
 engineer focus QA and software
 engineer focus

Slide 24

Slide 24 text

How many things can go wrong x1 x2 y2 z1 x3 y1 z2 z3 x1 x2 x5 x6 x3 x4 x7 x8 • Same context, reasonable overhead • less things can break • Different context, a lot to keep in mind • more things can break

Slide 25

Slide 25 text

Business starts learning from here x1 x2 y2 z1 x3 y1 z2 z3 x1 x2 x5 x6 x3 x4 x7 x8 • Same context, reasonable overhead • less things can break • business can iterate fast • Different context, a lot to keep in mind • more things can break • business can’t iterate fast “We’ve learned that you don’t have to build this” “We’ve learned that you didn’t have to build this”

Slide 26

Slide 26 text

SHIP FAST

Slide 27

Slide 27 text

? x2 x3 x1

Slide 28

Slide 28 text

Coding Testing and review x2 x3 x1 Release candidate

Slide 29

Slide 29 text

⌚ ??? Coding Testing and review x2 x3 x1 Release candidate

Slide 30

Slide 30 text

Manual tests Others What’s new Build apk Internal distrib Internal build Coding Testing and review x2 x3 x1 Release candidate

Slide 31

Slide 31 text

Sign
 apk Release
 notes Internal
 distrib Update assets Store
 upload RC distribution Coding Testing and review x2 x3 x1 Release candidate

Slide 32

Slide 32 text

+2h x number of loops +2h Coding Testing and review x2 x3 x1 Release candidate Internal build RC distrib x Rollout +1-2 days

Slide 33

Slide 33 text

+2h Coding Testing and review x2 x3 x1 Release candidate Internal build RC distrib x Rollout +1-2 days +2h x number of loops

Slide 34

Slide 34 text

clean the project unit tests tests coverage lint checks assemble apk distribute notify INTERNAL BUILD Coding Testing and review

Slide 35

Slide 35 text

INTERNAL BUILD $ ./gradlew clean testDebugUnitTest testDebugUnitTestCoverage lintStaging assembleStaging crashlyticsUploadDistributionStaging clean the project unit tests tests coverage lint checks assemble apk distribute notify pipeline { agent { node { label 'mobile' }} //... stages { stage("Build and distribute debug") { steps { } } } }

Slide 36

Slide 36 text

New feature branch On demand Create release branch Build and distribute RC Create Merge Request Notify reviewer New 
 release candidate Create merge request Bump version code 4 clicks 10 clicks 3 lines of code Push to master Push to Play Store beta Production app quick tests Store release Build, check, distribute apk 30min of clicking Trigger Actions Savings

Slide 37

Slide 37 text

Run all tests Create report Send report to slack Nightly tests Setup and run simulators Every 24hrs

Slide 38

Slide 38 text

Every 24hrs Update 
 /res/values Remove unused resources create merge request Update translations Fetch translation sfrom API lint checks

Slide 39

Slide 39 text

apply plugin: 'io.fabric' apply plugin: 'jacoco' apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'com.github.triplet.play' def process_language(language): print('Starting processing of: ' + language) is_default_language = language == ApiConfig.DEFAULT_LANGUAGE translations = translations_api.get_translations() if '--android' in sys.argv: parsed_keys = transform_all_translations(translations) android_exporter = ATranslationsExporter(parsed_keys, lang_code) android_exporter.generate_translations() if is_default_language: android_exporter.cleanup_android_new_translations() pipeline { triggers { gitlab(triggerOnPush: true, branchFilterType: 'All') } stage("Build debug") { when { branch 'dev' } steps { sh './gradlew clean assembleDebug --stacktrace' } } }

Slide 40

Slide 40 text

Coding Testing and review x2 x3 x1 Release candidate Internal build RC distrib auto x Rollout auto +1-2 days

Slide 41

Slide 41 text

* QUALITY ASSURANCE

Slide 42

Slide 42 text

Key feature can be tested manually only once.

Slide 43

Slide 43 text

RC QA fast feedback QA Development 3 STAGES OF QA Unit tests Lint checks Manual tests Functional tests End-to-end tests Test infra maintenance

Slide 44

Slide 44 text

LINT CHECKS

Slide 45

Slide 45 text

UNIT TESTING

Slide 46

Slide 46 text

Written by devs - Test single units 100% of tests must pass ✅

Slide 47

Slide 47 text

@Test
 public void testInit_whenConditionsMet_thenShouldShowRatingBox() {
 when(rateBoxManager.shouldAskForRate()).thenReturn(true);
 activityPresenter.initView(); verify(activityMock, once()).showRateBox(); } >200sec to find this screen/popup <1sec to execute test

Slide 48

Slide 48 text

@Test
 public void testCriteria_whenAllMet_thenShouldShowRateBox() {
 when(appDataMock.getAppInstallTime()).thenReturn(DATE_7_DAYS_AGO);
 when(rateBoxReminderMock.isDelayed()).thenReturn(false); assertEquals(rateBoxManager.shouldAskForAppRate(), true);
 } @Test
 public void testCriteria_whenAppFreshlyInstalled_thenShouldntShowRateBox() { when(appDataMock.getAppInstallTime()).thenReturn(DATE_TODAY);
 when(rateBoxReminderMock.isDelayed()).thenReturn(true);
 assertEquals(rateBoxManager.shouldAskForAppRate(), false);
 } CRITERIA UNIT TESTS

Slide 49

Slide 49 text

COVERAGE 100% (?)

Slide 50

Slide 50 text

@Test
 public void testSetGet() {
 assertFalse(model.isSet()); model.setItTo(true);
 assertTrue(model.isSet()); model.setItTo(false); assertFalse(model.isSet());
 } COVERAGE 100% @Test
 public void testConstructor() {
 model = new Model(true);
 assertTrue(model.isSet());
 }

Slide 51

Slide 51 text

2 unit tests, 0 integration tests

Slide 52

Slide 52 text

COVERAGE 50-60% & FAST FORWARD TO QA COVERAGE 100%

Slide 53

Slide 53 text

INTEGRATION TESTING

Slide 54

Slide 54 text

Built by QA engineers Run on emulator Hermetic env

Slide 55

Slide 55 text

Data and settings Use cases, presentation logic

Slide 56

Slide 56 text

mocks, init config System under test Data and settings Use cases, presentation logic

Slide 57

Slide 57 text

mocks System under test Use cases, presentation logic

Slide 58

Slide 58 text

mocks Screen by screen flow: - Splash - Login - Main Screen - List if items API calls + database operations Screen by screen flow: - Splash - Login - Main Screen - List if items API calls + database operations 5s 5s 5s 0s

Slide 59

Slide 59 text

Android instrumentation test template AVD manager System under testing

Slide 60

Slide 60 text

Android instrumentation test template Fake data (e.g. json file) AVD manager System under testing Mock objects

Slide 61

Slide 61 text

Android instrumentation test template Init state Activity System under testing Fake data (e.g. json file) AVD manager Mock objects

Slide 62

Slide 62 text

@RunWith(AndroidJUnit4.class) public class ForceAppUpdate_MockTests extends AzimoTestContainer { @Rule public DaggerMockRule daggerRule = DaggerMockUtils.defaultDaggerMockRule(); @Mock ApiVersionChecker apiVersionChecker; public void setupLogic() { when(apiVersionChecker.shouldUpdateApplication()).thenReturn(true); } @Test public void testForceAppUpdate_ifAppIsTooOld_shouldShowUpdateAzimoScreen() throws Exception { /* Data mocking (App-State unrelated) */ when(apiVersionChecker.shouldUpdateApplication()).thenReturn(true); /* Config init */ TestLauncher.init() .setting_setInputToUser(TestUsers.getTestUserNoTransfersNoRecipients()) .setting_useStartingPoint(LauncherStartingCondition.USER) .run(); /* Test Start */ appTooOldNavi().waitForEnteredActivity(AppTooOldActivity.class); appTooOldAsserts().assertThat_correctTitleIsDisplayed(); appTooOldAsserts().assertThat_correctDescriptionIsDisplayed(); appTooOldAsserts().assertThat_correctButtonDescriptionIsDisplayed(); } }

Slide 63

Slide 63 text

@RunWith(AndroidJUnit4.class) public class ForceAppUpdate_MockTests extends AzimoTestContainer { @Rule public DaggerMockRule daggerRule = DaggerMockUtils.defaultDaggerMockRule(); @Mock ApiVersionChecker apiVersionChecker; public void setupLogic() { when(apiVersionChecker.shouldUpdateApplication()).thenReturn(true); } @Test public void testForceAppUpdate_ifAppIsTooOld_shouldShowUpdateAzimoScreen() throws Exception { /* Data mocking (App-State unrelated) */ when(apiVersionChecker.shouldUpdateApplication()).thenReturn(true); /* Config init */ TestLauncher.init() .setting_setInputToUser(TestUsers.getTestUserNoTransfersNoRecipients()) .setting_useStartingPoint(LauncherStartingCondition.USER) .run(); /* Test Start */ appTooOldNavi().waitForEnteredActivity(AppTooOldActivity.class); appTooOldAsserts().assertThat_correctTitleIsDisplayed(); appTooOldAsserts().assertThat_correctDescriptionIsDisplayed(); appTooOldAsserts().assertThat_correctButtonDescriptionIsDisplayed(); } } Mocking Init state activity test

Slide 64

Slide 64 text

END-TO-END TESTING

Slide 65

Slide 65 text

Simulate end user 5 End-to-end features Up to 2 hours ⏲

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

How to get there ? 
 Be 1% better every release

Slide 68

Slide 68 text

early 2015 - no unit tests (product launch) late 2015 - 20% coverage, manual testing 2016 - 40% coverage, started QA engineering 2017 - 60% coverage, core features e2e 2018 - 50-60% coverage, all features e2e someday - “Lint zero” project

Slide 69

Slide 69 text

Automation Test Supervisor Automated test emulator run plugin Check on github.com/AzimoLabs

Slide 70

Slide 70 text

Testing Fast feedback QA Release candidate Internal build RC distrib auto min x Rollout auto asap RC testing x2 x3 x1 Development Coding Code checks ✔ ✔ ✔

Slide 71

Slide 71 text

Testing Fast feedback QA Release candidate Internal build RC distrib auto min x Rollout auto asap RC testing x2 x3 x1 Development Coding Code checks ✔ ✔ ✔

Slide 72

Slide 72 text

Write code that matters 8 Separate logic from SDK / Code must be testable

Slide 73

Slide 73 text

IF WRITING TESTS IS HARD, THAT MIGHT BE A BUG

Slide 74

Slide 74 text

Activity Data Navigation Networking UI View logic ALL-IN-ONE Testing? External QA, only end-to-end.

Slide 75

Slide 75 text

Model Presenter View Database Navigation API View logic TESTABLE Data logic Unit testing - moderate (a lot to mock, incl. SDK) Integration testing - moderate End-to-end testing - easy Stores

Slide 76

Slide 76 text

View Stores Navigator API View logic Use cases Repositories, Managers 5-STARS Database

Slide 77

Slide 77 text

UNIT TESTING View Stores Navigator API View logic Use cases Repositories, Managers Database

Slide 78

Slide 78 text

View Stores Navigator API View logic Use cases Repositories, Managers Database UNIT TESTING

Slide 79

Slide 79 text

View Stores Navigator API View logic Use cases Repositories, Managers Database UNIT TESTING

Slide 80

Slide 80 text

System under test Mocks INTEGRATION TESTING View Stores Navigator API View logic Use cases Repositories, Managers Database

Slide 81

Slide 81 text

System under test View Stores Navigator API View logic Use cases Repositories, Managers Database END-TO-END TESTING

Slide 82

Slide 82 text

EASY TO “THREAD” Always UI Ensure background thread Always background View Stores Navigator API View logic Use cases Repositories, Managers Database

Slide 83

Slide 83 text

SDK VS LOGIC Android SDK Plain code View Stores Navigator API View logic Use cases Repositories, Managers Database

Slide 84

Slide 84 text

THE BEST ARCHITECTURE The one that makes the developers team the most productive.

Slide 85

Slide 85 text

ARCHITECTURE PATTERNS • MVP, MVVM • App Architecture Components • Others (see Android Blueprints) github.com/googlesamples/
 android-architecture

Slide 86

Slide 86 text

WRITE LOGIC THAT MATTERS! GENERATE EVERYTHING ELSE. 8

Slide 87

Slide 87 text

@BindView(R.id.tvTitle) TextView tvTitle; @BindColor(R.color.brand) int colorBrand; @BindString(R.string.hello) String strHello; @Override protected void onCreate(Bundle state) { // LOGIC THAT MATTERS TO YOU } WITH BUTTERKNIFE

Slide 88

Slide 88 text

private TextView tvTitle; private String title; private int colorBrand; @Override protected void onCreate(Bundle state) { // A LITTLE BIT OF INIT tvTitle = findViewById(R.string.project_id); colorBrand = ContextCompat.getColor(this, R.color.brand); strHello = getString(R.string.strHello); // A LITTLE BIT OF LOGIC } WITHOUT BUTTERKNIFE

Slide 89

Slide 89 text

@Inject ActivityPresenter presenter; @Inject AnimationUtils animationUtils; @Inject ListAdapter listAdapter; @Override protected void onCreate(Bundle state) { // LOGIC THAT MATTERS TO YOU } WITH DAGGER

Slide 90

Slide 90 text

ActivityPresenter presenter; AnimationUtils animationUtils; ListAdapter listAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // A LITTLE BIT OF INIT presenter = new ActivityPresenter(this, /* x other dependencies */); animationUtils = new AnimationUtils(this); listAdapter = new listAdapter(this, /* y other dependencies*/); // A LITTLE BIT OF LOGIC
 // … } WITHOUT DAGGER

Slide 91

Slide 91 text

MORE DAGGER (INJECT EVERYTHING) @ActivityScope public class SupportAdapter extends SimplestRecyclerViewAdapter { public static final int ITEM_VIEW_TYPE_CATEGORY = 0; //…and 11 other types //Factories for all items types private final Map vhFactories; @Inject public SupportAdapter(Map vhFactories) { this.vhFactories = vhFactories; } @Override public SupportViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //Take factory for given view type and create ViewHolder return vhFactories.get(viewType).createViewHolder(parent); } }

Slide 92

Slide 92 text

@Module abstract class BindingsModule { @Binds @IntoMap @IntKey(ITEM_VIEW_TYPE_LOADING) abstract SupportViewHolderFactory bindLoadingViewHolderFactory(LoadingViewHolderFactory factory); @Binds @IntoMap @IntKey(ITEM_VIEW_TYPE_ARTICLE) abstract SupportViewHolderFactory bindArticleViewHolderFactory(ArticleViewHolderFactory factory); @Binds @IntoMap @IntKey(ITEM_VIEW_TYPE_CTA_CHAT) abstract SupportViewHolderFactory bindCtaChatViewHolderFactory(CtaChatViewHolderFactory factory); // and all other factories… }

Slide 93

Slide 93 text

@AutoFactory(implementing = SupportViewHolderFactory.class) public class ArticleViewHolder extends SupportViewHolder { @BindView(R2.id.text) TextView text; @BindView(R2.id.divider) View divider; ArticleViewModel viewModel; private final ArticleListener host; public ArticleViewHolder(@Provided LayoutInflater inflater, @Provided ArticleListener host, ViewGroup parent) { super(inflater, parent, R.layout.as_item_support_article); this.host = host; } @Override public void bind(SupportViewModel item) { viewModel = (ArticleViewModel) item; text.setText(viewModel.title); ViewUtils.visibility(divider, !viewModel.last); } @OnClick(R2.id.contentContainer) void itemClicked() { host.articleSelected(viewModel); } } Logic that matters! Code generated for us

Slide 94

Slide 94 text

DAGGER2RECIPES github.com/frogermcs • Async injection • MultiBinding + Autofactory • Demo app with all-in-one • Multimodule demo app

Slide 95

Slide 95 text

void handleApiErrorResponse(HttpException e) { GeneralErrorResponse errorResponse = errorResponseParser.valueOf(e.response()); switch (errorResponse.genericErrorCode) { case ErrorCodes.CODE_401_DEVICE_NOT_AUTHENTICATED: ((BaseActivity) activityCompanion).logoutUser(); break; case ErrorCodes.CODE_403_AUTHENTICATE_ACCOUNT_BLOCKED: ((BaseActivity) activityCompanion).showAccountBlockedError(); break; case ErrorCodes.CODE_422_USER_EMAIL_ALREADY_TAKEN: if (activityCompanion instanceof CreateAccountActivity) { ((CreateAccountActivity) activityCompanion).showEmailAlreadyRegistered(); } break; case ErrorCodes.CODE_404_USER_INVALID_INVIATION: if (activityCompanion instanceof CreateAccountActivity) { ((CreateAccountActivity) activityCompanion).showInvalidInvitation(); } break; default: handleGeneralError(errorResponse, e); break; } }

Slide 96

Slide 96 text

ERROR HANDLER @AutoHandler public interface SimplerErrorListener { @ErrorCode(CODE_403_AUTHENTICATE_ACCOUNT_BLOCKED) void accountBlocked(); @ErrorCode(CODE_422_USER_EMAIL_ALREADY_TAKEN) void emailTaken(); @ErrorCode(codes = {"501", "503"}) void serverError(); } Check on github.com/AzimoLabs /Api-Error-Handler

Slide 97

Slide 97 text

MODULARIZE ⚛

Slide 98

Slide 98 text

Objects graph CODE MODULES ApiModule DataModule UtilsModule UserManager CountriesRepo CurrenciesRepo … OKHttpClient ApiRestClient AuthInterceptor LoggingInterceptor CurrencyFormatter ExchangeCalculator … more…

Slide 99

Slide 99 text

Objects graph (AppComponent) ApiModule DataModule UtilsModule more… @Singleton @Component( modules = { AppModule.class, DataModule.class, ApiModule.class, UtilsModule.class, } ) public interface AppComponent { }

Slide 100

Slide 100 text

Objects graph (AppComponent) ApiModule DataModule UtilsModule more… @Module public class ApiModule { @Provides @Singleton public OkHttpClient provideOkHttpClient() {/**/} @Provides @Singleton public Retrofit provideRestAdapter(OkHttpClient okHttpClient) {/**/} @Provides @Singleton public GithubApiService provideGithubApiService(Retrofit restAdapter) { return restAdapter.create(GithubApiService.class); } }

Slide 101

Slide 101 text

Objects graph (AppComponent) ApiModule DataModule UtilsModule more… Our DataModule >600 lines of code

Slide 102

Slide 102 text

Objects graph (AppComponent) FEATURE MODULES CountriesModule PaymentModule CardsModule PaymentManager PaymentConfigRepo PaymentApiService CountriesManager CountriesRepository CountriesConfig CountriesApi CardsManager CardsRepository CardsEncryption CardsTokeniseApi more…

Slide 103

Slide 103 text

CountriesModule PaymentModule CardsModule DataModule vs > ? @ ? > @

Slide 104

Slide 104 text

@Singleton @Component( modules = { AzimoAppModule.class, DataModule.class, ApiModule.class, AppConfigModule.class, UtilsModule.class, BackendModule.class, GcmModule.class, ApiAuthorisationModule.class, ViewMappersModule.class, AnalyticsModule.class, MatchingModule.class, CardsModule.class, FormattersModule.class, TransactionDownloadModule.class, PeopleDownloadModule.class, AppsTrackingModule.class, DocUploaderModule.class, PricingModule.class, ZendeskModule.class, PayinModule.class, CheckoutModule.class, SharedEverywhereModule.class, PaymentModule.class, RateModule.class, EstimatedDeliveryModule.class, ZendeskModule.class, UserFactsModule.class, UserFactsDownloadModule.class, FirebaseCardsApiModule.class } ) public interface AppComponent { }

Slide 105

Slide 105 text

No content

Slide 106

Slide 106 text

MODULARIZE MORE ⚛ ⚛ ⚛

Slide 107

Slide 107 text

MULTI-FEATURE APP SupportFeature App ChatModule FAQModule Support logic PaymentFeature CardsModule Payments logic PaymentModule Base Shared features … CountriesFeature CurrenciesFeature UserFactsFeature … …

Slide 108

Slide 108 text

SupportFeature App ChatModule FAQModule Support logic PaymentFeature CardsModule Payments logic PaymentModule Base Shared features … CountriesFeature CurrenciesFeature UserFactsFeature … … • Quick compilation • Quick unit tests • Clean structure MULTI-FEATURE APP

Slide 109

Slide 109 text

SupportFeature App ChatModule FAQModule Support logic PaymentFeature CardsModule Payments logic PaymentModule Base Shared features … CountriesFeature CurrenciesFeature UserFactsFeature … … • Configuration 
 complexity • Low solutions 
 maturity • High entry level MULTI-FEATURE APP

Slide 110

Slide 110 text

THE REALITY SupportFeature App Base Shared features Still App… Countries Feature UserFacts Feature … PaymentFeature … Transactions
 Feature Still App…

Slide 111

Slide 111 text

PROJECT MODULARIZATION Multi module setup

Slide 112

Slide 112 text

PROJECT MODULARIZATION Multi module setup Dagger 2 across modules

Slide 113

Slide 113 text

PROJECT MODULARIZATION Multi module setup Dagger 2 across modules Unit testing

Slide 114

Slide 114 text

PROJECT MODULARIZATION Multi module setup Dagger 2 across modules Unit testing Tests 
 coverage

Slide 115

Slide 115 text

PROJECT MODULARIZATION Multi module setup Dagger 2 across modules Unit testing Tests 
 coverage github.com/frogermcs/MultiModuleGithubClient End-to-end testing Proguard configuration more in the future… Done during:

Slide 116

Slide 116 text

?A PEOPLE

Slide 117

Slide 117 text

Crash-free users >99.5% Release once a week

Slide 118

Slide 118 text

!!! Crash-free users >99.5% Release once a week

Slide 119

Slide 119 text

✋ STOP THE LINE

Slide 120

Slide 120 text

Every employee on the assembly line has a responsibility to push a big red button that stops everything whenever they notice a defect

Slide 121

Slide 121 text

Release every week + stop the line 40 releases in last 12 months.

Slide 122

Slide 122 text

Release weeks in last 12 months

Slide 123

Slide 123 text

Release once a week - achieved in 83% Crash free >99.5% - achieved in 100%

Slide 124

Slide 124 text

THEY TELL We don’t have time do it right YOU ASK Do you have time to do it twice?

Slide 125

Slide 125 text

@froger_mcs [email protected] medium.com/@froger_mcs Link to this presentation will be there More about how we build the technology Ask me anything you want :)