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

Testando o App do Nubank

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →

Testando o App do Nubank

Nesta palestra, compartilharemos técnicas que utilizamos para tornar nosso código fácil de testar e quais os tipos de testes que fazemos nos apps mobile do Nubank para garantir qualidade em todas as condições de uso.

Avatar for Victor Maraccini

Victor Maraccini

December 07, 2018

More Decks by Victor Maraccini

Other Decks in Programming

Transcript

  1. • Mais de 1200 funcionários • Diversidade • Mais de

    5MM de clientes • Clientes fazem tudo pelo app • Cultura de qualidade e testes O que é Nubank?
  2. Show of hands Quem aqui: • É cliente do Nubank

    • Mexeu com Mobile • Escreveu teste para Mobile
  3. Por que testamos? • Evitar regressão • Mais velocidade para

    refatorar e fazer mudanças • Novos engenheiros tem confiança nas entregas • Garantir comportamentos existentes • De forma automatizada • Em diferentes condições de uso
  4. Nossa pirâmide de testes em Mobile Integração Unitários E2E Testes

    antes de cada merge Deploy a cada merge Rollout Teste A/B Checklist
  5. struct MGMChannelData { let type: MGMChannel } struct MGMChannelData {

    let type: MGMChannel let count: Int? let canOpen: Bool } Testes unitários Integração Unitários
  6. Testes unitários Integração Unitários struct MGMChannelData { let type: MGMChannel

    let count: Int? let canOpen: Bool } struct MGMChannelData { let count: Int? }
  7. Testes unitários Integração Unitários struct MGMChannelData { let type: MGMChannel

    let count: Int? let canOpen: Bool } struct MGMChannelData { let canOpen: Bool }
  8. Testes unitários Exemplo: Funções puras Integração Unitários struct MGMChannelData {

    let type: MGMChannel let count: Int? let canOpen: Bool } class MGMInteractor { ... func createChannelsData(urlHandler: URLHandler, invitations: Int, channels: [Customer.Channel]) -> [MGMChannelData] }
  9. Testes unitários Exemplo: Funções puras func testChannelDataCreationEmailNoInvites() { } Integração

    Unitários struct MGMChannelData { let type: MGMChannel let count: Int? let canOpen: Bool } class MGMInteractor { ... func createChannelsData(urlHandler: URLHandler, invitations: Int, channels: [Customer.Channel]) -> [MGMChannelData] }
  10. Testes unitários Exemplo: Funções puras func testChannelDataCreationEmailNoInvitesCannotOpen() { let urlHandler

    = URLHandlerMock(canOpenWhatsapp: false, canOpenFacebook: false) let invites = 0 let channelData = MGMInteractor.createChannelData(invitations: invites, channel: .email, urlHandler: urlHandler) expect(channelData) == MGMChannelData(type: .email, count: invites, canOpen: false) } Integração Unitários struct MGMChannelData { let type: MGMChannel let count: Int? let canOpen: Bool } class MGMInteractor { ... func createChannelsData(urlHandler: URLHandler, invitations: Int, channels: [Customer.Channel]) -> [MGMChannelData] }
  11. Testes unitários Exemplo: Funções puras func testChannelDataCreationEmailNoInvitesCannotOpen() { let urlHandler

    = URLHandlerMock(canOpenWhatsapp: false, canOpenFacebook: false) let invites = 0 let channelData = MGMInteractor.createChannelData(invitations: invites, channel: .email, urlHandler: urlHandler) expect(channelData) == MGMChannelData(type: .email, count: invites, canOpen: false) } Integração Unitários let invites = 0 canOpen: false struct MGMChannelData { let type: MGMChannel let count: Int? let canOpen: Bool } class MGMInteractor { ... func createChannelsData(urlHandler: URLHandler, invitations: Int, channels: [Customer.Channel]) -> [MGMChannelData] } channel: .email,
  12. Testes unitários Exemplo: Funções puras func testChannelDataCreationEmailPositiveNumberOfInvitesCanOpen() { } Integração

    Unitários struct MGMChannelData { let type: MGMChannel let count: Int? let canOpen: Bool } class MGMInteractor { ... func createChannelsData(urlHandler: URLHandler, invitations: Int, channels: [Customer.Channel]) -> [MGMChannelData] }
  13. Testes unitários Exemplo: Funções puras func testChannelDataCreationEmailPositiveNumberOfInvitesCanOpen() { let urlHandler

    = URLHandlerMock(canOpenWhatsapp: false, canOpenFacebook: false) let invites = Int.arbitrary.suchThat { $0 > 0 }.generate let channelData = MGMInteractor.createChannelData(invitations: invites, channel: .email, urlHandler: urlHandler) expect(channelData) == MGMChannelData(type: .email, count: invites, canOpen: true) } Integração Unitários struct MGMChannelData { let type: MGMChannel let count: Int? let canOpen: Bool } class MGMInteractor { ... func createChannelsData(urlHandler: URLHandler, invitations: Int, channels: [Customer.Channel]) -> [MGMChannelData] }
  14. Testes unitários Exemplo: Funções puras func testChannelDataCreationEmailPositiveNumberOfInvitesCanOpen() { let urlHandler

    = URLHandlerMock(canOpenWhatsapp: false, canOpenFacebook: false) let invites = Int.arbitrary.suchThat { $0 > 0 }.generate let channelData = MGMInteractor.createChannelData(invitations: invites, channel: .email, urlHandler: urlHandler) expect(channelData) == MGMChannelData(type: .email, count: invites, canOpen: true) } Integração Unitários let invites = Int.arbitrary.suchThat { $0 > 0 }.generate canOpen: true struct MGMChannelData { let type: MGMChannel let count: Int? let canOpen: Bool } class MGMInteractor { ... func createChannelsData(urlHandler: URLHandler, invitations: Int, channels: [Customer.Channel]) -> [MGMChannelData] } channel: .email,
  15. Testes unitários Exemplo: Efeitos colaterais func testInviteMGM() { let mgmManager

    = StubMGMManager() let stubViewController = StubViewController() let controller = Controller(accountStatus: .just(.active), mgmManager: mgmManager, viewController: stubViewController) //Given a simulated tap stubViewController.confirmSubject.onNext(()) //Expect side-effects expect(mgmManager.inviteInvocationCount) == 1 } Integração Unitários
  16. Testes unitários Exemplo: Efeitos colaterais func testInviteMGM() { let mgmManager

    = StubMGMManager() let stubViewController = StubViewController() let controller = Controller(accountStatus: .just(.active), mgmManager: mgmManager, viewController: stubViewController) //Given a simulated tap stubViewController.confirmSubject.onNext(()) //Expect side-effects expect(mgmManager.inviteInvocationCount) == 1 } Integração Unitários let stubViewController = StubViewController() viewController: stubViewController //Given a simulated tap stubViewController.confirmSubject.onNext(())
  17. func testInviteMGM() { let mgmManager = StubMGMManager() let stubViewController =

    StubViewController() let controller = Controller(accountStatus: .just(.active), mgmManager: mgmManager, viewController: stubViewController) //Given a simulated tap stubViewController.confirmSubject.onNext(()) //Expect side-effects expect(mgmManager.inviteInvocationCount) == 1 } //Expect side-effects expect(mgmManager.inviteInvocationCount) == 1 let mgmManager = StubMGMManager() mgmManager: mgmManager, Testes unitários Exemplo: Efeitos colaterais Integração Unitários
  18. Testes unitários de view Integração Unitários • Permite validar o

    layout em várias condições de uso • Uso de screenshots • Facilita a verificação com o Design • Evita modificações não intencionais no layout • Permite validar a acessibilidade das telas
  19. Testes unitários de view Integração Unitários Exercitar todos os tamanhos

    da tela ou componente * Telas não necessariamente verdadeiras 4S 6/6S/7/8 6P/7P/8P 5/5S/5C/SE X
  20. Testes unitários de view Integração Unitários Exercitar todos os tamanhos

    de fonte na tela ou componente CategoryXS CategoryXXXL CategoryAccessibilityXXXL * Telas não necessariamente verdadeiras
  21. Testes unitários de view Integração Unitários Exercitar todos os estados

    da tela ou componente * Telas não necessariamente verdadeiras
  22. Testes unitários de view Exemplo func testLayout() throws { let

    permutations = [ ("popup", PopupViewModel(popup: somePopup)), ("one-action", PopupViewModel(popup: oneActionPopup)), ("no-icon", PopupViewModel(popup: noIconPopup)), ("recognized-timeout", .recognizedTimeout(url: .empty())), ("unrecognized-block", .unrecognizedBlock(url: .empty())) ] try permutations.forEach { identifier, viewModel in let viewController = PopupViewController() viewController.bind(viewModel) try assertView(viewController.view, width: 350, identifier: identifier, screenshotService: service) } } Integração Unitários
  23. Testes unitários de view Exemplo func testLayout() throws { let

    permutations = [ ("popup", PopupViewModel(popup: somePopup)), ("one-action", PopupViewModel(popup: oneActionPopup)), ("no-icon", PopupViewModel(popup: noIconPopup)), ("recognized-timeout", .recognizedTimeout(url: .empty())), ("unrecognized-block", .unrecognizedBlock(url: .empty())) ] try permutations.forEach { identifier, viewModel in let viewController = PopupViewController() viewController.bind(viewModel) try assertView(viewController.view, width: 350, identifier: identifier, screenshotService: service) } } Integração Unitários let permutations = [ ("popup", PopupViewModel(popup: somePopup)), ("one-action", PopupViewModel(popup: oneActionPopup)), ("no-icon", PopupViewModel(popup: noIconPopup)), ("recognized-timeout", .recognizedTimeout(url: .empty())), ("unrecognized-block", .unrecognizedBlock(url: .empty())) ]
  24. Testes unitários de view Exemplo func testLayout() throws { let

    permutations = [ ("popup", PopupViewModel(popup: somePopup)), ("one-action", PopupViewModel(popup: oneActionPopup)), ("no-icon", PopupViewModel(popup: noIconPopup)), ("recognized-timeout", .recognizedTimeout(url: .empty())), ("unrecognized-block", .unrecognizedBlock(url: .empty())) ] try permutations.forEach { identifier, viewModel in let viewController = PopupViewController() viewController.bind(viewModel) try assertView(viewController.view, width: 350, identifier: identifier, screenshotService: service) } } Integração Unitários let viewController = PopupViewController() viewController.bind(viewModel) try permutations.forEach { identifier, viewModel in }
  25. Testes unitários de view Exemplo func testLayout() throws { let

    permutations = [ ("popup", PopupViewModel(popup: somePopup)), ("one-action", PopupViewModel(popup: oneActionPopup)), ("no-icon", PopupViewModel(popup: noIconPopup)), ("recognized-timeout", .recognizedTimeout(url: .empty())), ("unrecognized-block", .unrecognizedBlock(url: .empty())) ] try permutations.forEach { identifier, viewModel in let viewController = PopupViewController() viewController.bind(viewModel) try assertView(viewController.view, width: 350, identifier: identifier, screenshotService: service) } } Integração Unitários try assertView(viewController.view, width: 350, identifier: identifier, screenshotService: service) try permutations.forEach { identifier, viewModel in }
  26. Testes de integração Integração Unitários • Foco no funcionamento dos

    componentes em conjunto • Fluxos reais do app com poucas modificações • Requests mockados internamente no teste
  27. Testes de integração Exemplo func login() { tester().tapView(withAccessibilityLabel: "LOGIN") tester().waitForSoftwareKeyboard()

    tester().enterText(intoCurrentFirstResponder: validCPF) tester().tapView(withAccessibilityLabel: "CONTINUAR") tester().enterText(intoCurrentFirstResponder: validPassword) tester().tapView(withAccessibilityLabel: "ENTRAR") } func testReorderFlow() throws { tester().swipeView(withAccessibilityIdentifier: "global-actions", inDirection: .left) tester().moveRow(at: IndexPath(row: 0, section: 0), to: IndexPath(row: 1, section: 0), in: tester().firstTableView()) tester().waitForAnimationsToFinish() tester().tapView(withAccessibilityLabel: "Voltar") tester().swipeView(withAccessibilityIdentifier: "global-actions", inDirection: .right) tester().waitForAnimationsToFinish() } Integração Unitários
  28. Testes de integração Exemplo func login() { tester().tapView(withAccessibilityLabel: "LOGIN") tester().waitForSoftwareKeyboard()

    tester().enterText(intoCurrentFirstResponder: validCPF) tester().tapView(withAccessibilityLabel: "CONTINUAR") tester().enterText(intoCurrentFirstResponder: validPassword) tester().tapView(withAccessibilityLabel: "ENTRAR") } func testReorderFlow() throws { tester().swipeView(withAccessibilityIdentifier: "global-actions", inDirection: .left) tester().moveRow(at: IndexPath(row: 0, section: 0), to: IndexPath(row: 1, section: 0), in: tester().firstTableView()) tester().waitForAnimationsToFinish() tester().tapView(withAccessibilityLabel: "Voltar") tester().swipeView(withAccessibilityIdentifier: "global-actions", inDirection: .right) tester().waitForAnimationsToFinish() } Integração Unitários tapView waitForSoftwareKeyboard enterText swipeView moveRow waitForAnimationsToFinish