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

Evolution of Test Automation at Spotify

Evolution of Test Automation at Spotify

Quality is important. People love a high-quality application. So, how can we achieve it? Spotify wants to release an application faster. We have a one-week release cycle. Every day, hundreds of engineers are working on the same code base. We believe that test automation is one of the key factors to keep high quality in a sustainable way.

Test automation has three levels: unit test, integration test, and UI test. In this talk, we will present how Spotify has evolved test automation for Mobile. Especially, we're going to talk on integration and UI test level including our internal systems.

Sangsoo Nam

May 31, 2019
Tweet

More Decks by Sangsoo Nam

Other Decks in Technology

Transcript

  1. Login Test Case - Click email input field - Type

    email - Click password input field - Type password - Click "Login" button
  2. Script Test Server Application Logic @Test public void testLogin() {

    clickEmailInputField(); type(“[email protected]"); clickPasswordInputField(); type(“password"); clickLoginButton(); }
  3. BeanShell Lightweight Scripting for Java Runs in the Java Runtime

    Environment Script Test Server Application Logic Script Engine
  4. Script Test Server Application Logic Script Engine email = findView("input_text_email");

    email.performClick(); email.setText("[email protected]") password = findView("input_text_password"); password.performClick(); password.setText("password") findView("button_login").performClick();
  5. Page Object Pattern Page Object allows to do and see

    
 that a user can on a page Hide the underlying mechanics and show abstract-level interfaces Page Page Object Test
  6. Login View public class LoginView { public void clickLogin() {

    ... } public void typeEmail(String email) { ... } public void typePassword(String password) { ... }
  7. Script Test Server Application Logic Script Engine @Test public void

    testLogin() { login.typeEmail(“[email protected]"); login.typePassword(“password"); login.clickLogin(); }
  8. Login Test Case @Test public void testLogin() { LoginAuto login

    = Pages.remote(LoginAuto.class); login.typeEmail("[email protected]"); login.typePassword("password"); login.clickLogin(); HomeAuto home = Pages.remote(HomeAuto.class); assertTrue(home.isDisplayed()); }
  9. Login Test Case @Test public void testLogin() { LoginAuto login

    = Pages.remote(LoginAuto.class); login.typeEmail("[email protected]"); login.typePassword("password"); login.clickLogin(); HomeAuto home = Pages.remote(HomeAuto.class); assertTrue(home.isDisplayed()); } Fail.. no guarantee to show immediately
  10. Login Test Case (Periodic Polling) @Test public void testLogin() {

    LoginAuto login = Pages.remote(LoginAuto.class); login.typeEmail("[email protected]"); login.typePassword("password"); login.clickLogin(); HomeAuto home = Pages.remote(HomeAuto.class); assertWaitTrue(home.isDisplayed(), Duration.ofSeconds(10).toMillis()); }
  11. Account Issues Spoiled Accounts Empty state verifications Playlist, recently played,

    search history, … Concurrent Access Playback with Connect
  12. TDS(Test Data Service) Provide a simple and scalable solution to

    acquire a Spotify user Use a user pool and support tagging users
 Don’t return the user currently being used
  13. Model Abstraction of the real world Consist of expected behavior

    and states of the system under the test Test cases are automatically generated from models
  14. Test Case Generation Smoke Test new AStarPath(new ReachedVertex(“v_Done”)) Functional Test

    new RandomPath(new EdgeCoverage(100)) Stability Test new RandomPath(new TimeDuration(30, TimeUnit.MINUTES))
  15. Model Interface public interface LoginModel {
 public void e_Init(); public

    void v_LoginPrompted(); public void e_InValidCredentials(); public void v_LoginFailed(); public void e_ValidCredentials(); public void v_InitialView(); }
  16. Model Interface Implementation public class LoginModelAPI implements LoginModel { private

    LoginAuto login; public void e_Init() { login = Pages.remote(LoginAuto.class); } public void e_ValidCredentials() { login.typeEmail("[email protected]"); login.typePassword("password"); login.clickLogin(); } ... }
  17. Problematic Test Pyramid Unit Tests E2E Tests QA Running E2E

    is slow… Problem could be found after merging
  18. E2E is ONLY option to test integration 
 with platform

    level components Backend services SpotifyProvider
 (Custom Content Provider) Android Services Core(C++) Metadata Features … Playback Session Ads …
  19. Cassette Capabilities - Perform, record, and reply network requests for

    any given user
 (support OKHttp and custom protocol used at Spotify) - Simulate service binds to a pre-configured Android services
 (control playback, product updates, …)
 - Simulate queries to a pre-configured ContentProvider - Check and assert on UI View hierarchy states - Simulate app states
 (foreground/background)
  20. public class ArtistHubFragmentIntegrationTest extends IntegrationTestCase { @Rule private final SpotifyCassetteRule

    mSpotifyCassetteRule = new SpotifyCassetteRule(); @Override protected Fragment createStartFragment(Flags flags, String testUsername) { return ArtistHubFragment.create("spotify:artist:0SfsnGyD8FpIN4U4WCkBZ5", "ARTIST", flags); } @Test @Cassette(user = @User(flags = AndroidUserFlags.PREMIUM)) public void testClickRelatedArtists() throws Throwable { Bdd.given((Setup<ArtistAuto>) () -> { return new ArtistAutoImpl((ArtistHubFragment) getFragment()); }).when((Action<ArtistAuto, ArtistRelatedArtistsAuto>) auto -> { auto.clickRelatedArtists(); return new RelatedArtistsAutoImpl((ArtistRelatedArtistsFragment) getNextFragment())); }).then(auto -> { assertTrue("Related Artists page did not load.", auto.isLoaded()) }); }
  21. public class ArtistHubFragmentIntegrationTest extends IntegrationTestCase { @Rule private final SpotifyCassetteRule

    mSpotifyCassetteRule = new SpotifyCassetteRule(); @Override protected Fragment createStartFragment(Flags flags, String testUsername) { return ArtistHubFragment.create("spotify:artist:0SfsnGyD8FpIN4U4WCkBZ5", "ARTIST", flags); } @Test @Cassette(user = @User(flags = AndroidUserFlags.PREMIUM)) public void testClickRelatedArtists() throws Throwable { Bdd.given((Setup<ArtistAuto>) () -> { return new ArtistAutoImpl((ArtistHubFragment) getFragment()); }).when((Action<ArtistAuto, ArtistRelatedArtistsAuto>) auto -> { auto.clickRelatedArtists(); return new RelatedArtistsAutoImpl((ArtistRelatedArtistsFragment) getNextFragment())); }).then(auto -> { assertTrue("Related Artists page did not load.", auto.isLoaded()) }); }
  22. Espresso - Concise, beautiful, and reliable Android UI tests -

    Support synchronization with idling resources - Instrumented test: Have access to instrumentation information. 
 e.g. the context of the app - Result with a recorded video, performance analysis, and more @Test public void greeterSaysHello() { onView(withId(R.id.name_field)).perform(typeText("Steve")); onView(withId(R.id.greet_button)).perform(click()); onView(withText("Hello Steve!")).check(matches(isDisplayed())); }
  23. Test Certified for Client Knowing • 100% code and test

    ownership registration • Number, type, and phase of current tests is known 
 Improving • Healthy distribution of unit tests, integration tests, and E2E tests • No static analysis warnings, no suppressed linter warnings 
 Maintaining • Do not decrease code coverage • Bug fixes require automated regression tests • New tests are written at the lowest level of complexity that provides the best value