O teste de snapshot é um método de verificação automatizada da interface do usuário onde o estado atual da tela é comparado com uma imagem de referência (snapshot) salva durante a execução anterior do teste. Qualquer discrepância visual é registrada como uma alteração que requer confirmação do desenvolvedor. Ao contrário dos testes de UI que verificam a presença de elementos, os testes de snapshot detectam mudanças em nível de pixel — deslocamentos, desvios de cor e problemas de layout. De acordo com Android Developers, 2024, o teste de snapshot detecta até 30% das regressões visuais que passam despercebidas nos testes de UI tradicionais, tornando-o uma ferramenta indispensável para manter uma interface consistente.
Principais pontos
O teste de snapshot (teste de instantâneo) é uma técnica na qual um teste renderiza um componente de interface, salva a imagem resultante como referência e, em execuções subsequentes, compara a renderização atual com essa referência. Se as imagens coincidirem — o teste passa. Se forem encontradas diferenças — o teste falha, e o desenvolvedor recebe uma imagem diff com os pixels alterados destacados. A técnica foi emprestada do desenvolvimento web (Jest snapshots) e adaptada para plataformas móveis.
O principal valor dos testes de snapshot é a detecção automática de mudanças visuais inesperadas. Um desenvolvedor pode alterar o esquema de cores em um tema global e afetar acidentalmente uma dúzia de telas. Testes de UI que verificam a presença de botões e textos não notarão isso. Um teste de snapshot capturará cada mudança de pixel em cada tela afetada, fornecendo uma imagem completa do impacto da alteração.
De acordo com a pesquisa do Mobile DevOps Summit 2023, equipes que usam testes de snapshot além dos testes de UI clássicos reduzem o número de defeitos visuais em lançamentos em 40%. Essa abordagem é especialmente eficaz em projetos com sistemas de design e arquiteturas baseadas em componentes, onde a alteração de um componente base pode afetar dezenas de telas da aplicação.
A diferença fundamental está no objeto de verificação. Os testes de UI verificam a presença, o estado e o comportamento dos elementos da interface: “o botão está visível”, “o texto contém uma mensagem de erro”, “ao pressionar abre uma nova tela”. Os testes de snapshot verificam a aparência visual completa: posicionamento dos elementos, margens, cores, fontes, sombras e bordas arredondadas. Um teste de snapshot responde à pergunta “a tela parece como esperado?”, enquanto um teste de UI responde “a tela funciona como esperado?”
A velocidade de execução também difere. Os testes de UI são executados em um emulador ou dispositivo real, exigem carregamento completo da aplicação e levam de 10 segundos a um minuto por cenário. Os testes de snapshot com bibliotecas como Paparazzi renderizam componentes em um ambiente virtual sem iniciar um emulador, reduzindo o tempo de teste para 100–500 milissegundos. Um conjunto completo de testes de snapshot (50–100 telas) é executado em 2–5 minutos em vez de 30–60 minutos para um conjunto comparável de testes de UI.
No entanto, os testes de snapshot não substituem os testes de UI. A estratégia ideal é uma combinação: os testes de snapshot cobrem a regressão visual (renderização de cada tela em estados básicos), enquanto os testes de UI cobrem os aspectos comportamentais (cenários de clique, validação de entrada, navegação). Essa combinação proporciona 90% de confiança na correção da interface com tempo mínimo de execução em CI.
No Android, as principais ferramentas são Paparazzi e Shot. O Paparazzi da Cash App renderiza componentes em um ambiente de teste JVM sem emulador, usando o layout de gravidade Layoutlib. O Shot da Karumi realiza screenshots de Instrumentation em um dispositivo real ou emulador e os compara com as referências através da biblioteca AShot, levando em conta diferenças de resolução e densidade de pixels.
Paparazzi não requer iniciar um emulador — a renderização é feita na JVM através do Layoutlib, proporcionando velocidade comparável a testes unitários. A biblioteca suporta tanto o sistema View quanto o Jetpack Compose. Para Compose, use o modificador paparazzi.snapshot { MyComposable() }. As referências são armazenadas em src/test/snapshots e comparadas automaticamente a cada execução. A porcentagem máxima de diferença é configurável através de maxPercentDifference.
SnapshotTesting da Point-Free suporta comparação não apenas de UIImage, mas também de strings, JSON, Data e stores inteiras do Core Data. Isso a torna uma ferramenta versátil não apenas para snapshots de UI, mas também para verificar serialização e decodificação de respostas JSON. Para SwiftUI, use a extensão assertSnapshot com o modificador .image(on: .iPhone13). A estratégia record: true cria referências na primeira execução.
Para React Native, a solução popular é react-native-testing-library combinada com jest-image-snapshot. A abordagem web de teste de snapshot é transportada para o ambiente móvel renderizando componentes em um ambiente Node.js, seguido pela comparação de snapshots JSON do DOM virtual. Essa abordagem é mais rápida que a nativa, mas menos precisa — ela não leva em conta as particularidades de renderização de fontes e componentes do sistema específicas da plataforma. Para Flutter, o teste golden é usado através do toolkit goldens.
Vamos ver testes de snapshot para Android (Paparazzi) e iOS (SnapshotTesting). Ambos os exemplos verificam a aparência de um componente — um cartão de usuário com avatar, nome e status. O teste renderiza o componente com dados de teste e compara o resultado com uma imagem de referência armazenada no repositório.
O Paparazzi usa a anotação @Test e o método snapshot() para capturar a renderização. As referências são salvas na pasta src/test/snapshots e carregadas automaticamente na próxima execução para comparação.
class UserCardSnapshotTest {
@get:Rule
val paparazzi = Paparazzi(
theme = "Theme.MyApp",
maxPercentDifference = 0.1
)
@Test
fun userCard_defaultState() {
val card = UserCard(
name = "Alice Johnson",
status = "Online",
avatarUrl = "https://example.com/avatar.png"
)
paparazzi.snapshot(card)
}
@Test
fun userCard_offlineState() {
val card = UserCard(
name = "Bob Smith",
status = "Offline",
avatarUrl = null
)
paparazzi.snapshot(card, name = "user_card_offline")
}
}
SnapshotTesting usa o modificador .snapshot() dentro de assertSnapshot. A biblioteca determina automaticamente o formato — UIImage para UIView, String para texto, Data para dados binários.
import SnapshotTesting
import XCTest
class UserCardSnapshotTests: XCTestCase {
func testUserCardDefaultState() {
let card = UserCardView(
name: "Alice Johnson",
status: "Online",
avatarURL: URL(string: "https://example.com/avatar.png")
)
let controller = UIHostingController(rootView: card)
assertSnapshot(matching: controller, as: .image(on: .iPhone13))
}
func testUserCardOfflineState() {
let card = UserCardView(
name: "Bob Smith",
status: "Offline",
avatarURL: nil
)
assertSnapshot(matching: card, as: .image(on: .iPhone13))
}
}
Um fluxo de trabalho típico inclui quatro etapas. Primeira execução (modo record): todos os testes de snapshot são executados em modo de gravação — as imagens de referência são criadas e salvas no repositório. Esta etapa é realizada durante a configuração inicial dos testes ou após uma alteração intencional na interface. Após a gravação, as referências são commitadas junto com o código — elas se tornam parte do projeto.
Nas execuções subsequentes, os testes funcionam em modo de comparação: cada nova renderização é comparada com a referência. Se forem encontradas diferenças, uma imagem diff é gerada: pixels que coincidem com a referência são destacados em verde, os diferentes em vermelho. O desenvolvedor estuda o diff e toma uma decisão: se a mudança for esperada (alteração consciente de design), a referência é atualizada com o comando record; se inesperada — o bug é corrigido. A atualização de referências é feita com um único comando: para Paparazzi é `./gradlew recordPaparazzi`, para SnapshotTesting — `assertSnapshot(record: true)`.
De acordo com o Spotify Engineering Blog (2022), equipes que usam o fluxo de trabalho descrito gastam em média 2 minutos por teste analisando imagens diff. Com um conjunto de 50 testes de snapshot, um ciclo completo de atualização de referências leva de 15 a 20 minutos, significativamente mais rápido do que a verificação manual de mudanças visuais em 50 telas.
Os testes de snapshot têm limitações fundamentais. Sensibilidade ao ambiente: um mesmo componente pode renderizar de forma diferente em diferentes versões do SO, densidades de tela e configurações de fonte. Referências criadas em uma máquina podem diferir da renderização em um servidor CI. A solução é usar parâmetros de ambiente fixos: uma versão específica do Layoutlib para Paparazzi ou um modelo exato de dispositivo para SnapshotTesting.
Antipadrão n.º 1: snapshots gigantes — um teste de snapshot que captura a tela inteira falha a cada mudança mínima em qualquer componente. A abordagem correta é testar componentes individuais (botão, cartão, campo de entrada) isoladamente. Cada componente é testado de forma independente, fornecendo identificação precisa da fonte da alteração. Antipadrão n.º 2: ignorar diffs — atualizar automaticamente as referências sem analisar as imagens diff reduz o valor dos testes de snapshot a zero. Cada diff requer uma decisão consciente do desenvolvedor.
De acordo com o Better Engineering Blog (2023), os testes de snapshot trazem maior valor quando cobrem componentes do sistema de design e telas principais em estados básicos — vazio, preenchido, erro e limite. Cobrir animações e estados dinâmicos com testes de snapshot é ineficiente devido à natureza não determinística dos timestamps na renderização — para esses cenários, gravação de vídeo ou verificação manual de QA são mais adequados.
Perguntas frequentes
Não, os testes de snapshot verificam a aparência visual, enquanto os testes de UI verificam o comportamento da interface. A estratégia ideal é combinar ambas as abordagens: snapshots para regressão visual, testes de UI para cenários e navegação. Snapshots respondem “parece certo?”, testes de UI respondem “funciona certo?”.
As referências são atualizadas a cada alteração consciente de design: uma nova cor de tema, margens modificadas, adição ou remoção de elementos. A atualização é feita através do modo record, após o qual as imagens diff são revisadas na revisão de código para garantir que as mudanças correspondam às expectativas do designer.
Em primeiro lugar, componentes do sistema de design — botões, cartões, campos de entrada, janelas modais. Em seguida, telas principais em estados básicos. Não teste com snapshots animações, WebView, mapas e telas com conteúdo dinâmico — snapshots produzem falsas falhas devido ao não determinismo.
Use o mesmo nível de API para os modos record e test. Para Paparazzi, especifique uma versão específica do Layoutlib na configuração. Para SnapshotTesting, fixe o modelo do dispositivo. Referências criadas no Android 14 podem diferir da renderização no Android 12 devido a mudanças nas fontes do sistema e no tema Material.
Em CI, os testes de snapshot são executados em modo de verificação (verify). Se um teste falhar, o CI mostra a imagem diff nos artefatos de build. O modo record (atualização de referências) é executado localmente pelo desenvolvedor ou em uma tarefa de CI separada com acionamento manual. As imagens de referência devem ser commitadas no repositório.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também