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

Testando o App do Nubank

Francesco
December 07, 2018

Testando o App do Nubank

Francesco

December 07, 2018
Tweet

More Decks by Francesco

Other Decks in Technology

Transcript

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

    5MM de clientes • Clientes fazem tudo pelo app O que é Nubank?
  2. • Mais de 1200 funcionários • Diversidade • Mais de

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

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

    refatorar e fazer mudanças • Novos engenheiros tem confiança nas entregas
  5. 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
  6. 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
  7. 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
  8. struct MGMChannelData { let type: MGMChannel } struct MGMChannelData {

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

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

    let count: Int? let canOpen: Bool } struct MGMChannelData { let canOpen: Bool }
  11. 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] }
  12. 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] }
  13. 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] }
  14. 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,
  15. 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] }
  16. 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] }
  17. 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,
  18. 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
  19. 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(())
  20. 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
  21. Testes unitários de view Integração Unitários • Permite validar o

    layout em várias condições de uso • Uso de screenshots
  22. 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
  23. 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
  24. 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
  25. Testes unitários de view Integração Unitários Exercitar todos os tamanhos

    da tela ou componente * Telas não necessariamente verdadeiras
  26. 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
  27. 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
  28. Testes unitários de view Integração Unitários Exercitar todos os estados

    da tela ou componente * Telas não necessariamente verdadeiras
  29. Testes unitários de view Integração Unitários Exercitar todos os estados

    da tela ou componente * Telas não necessariamente verdadeiras
  30. 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
  31. 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())) ]
  32. 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 }
  33. 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 }
  34. Testes de integração Integração Unitários • Foco no funcionamento dos

    componentes em conjunto • Fluxos reais do app com poucas modificações
  35. 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
  36. 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
  37. 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