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

SnapshotTesting

 SnapshotTesting

Ce discours porte sur la manière d'exploiter les tests de snapshot dans les aperçus Xcode pour augmenter la couverture de code de votre application.

Avatar for Jeffrey Macko

Jeffrey Macko

April 25, 2023
Tweet

More Decks by Jeffrey Macko

Other Decks in Programming

Transcript

  1. Présentation du SnapshotTesting • SnapshotTesting, une technique de test unitaire

    qui consiste à capturer des données et à les comparer à une référence. • Cette comparaison permet de mieux comprendre l'impact des modifications et de détecter les régressions. • C'est une méthode simple, rapide à mettre en place et fiable. • Elle permet de visualiser clairement les changements lors Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  2. Exemple Tester un bouton Jeffrey MACKO Tech Lead iOS Vitamin

    - Mobile Days 25/04 - Decathlon | Technology
  3. import Foundation import SnapshotTesting import XCTest import SwiftUI @testable import

    VitaminPlay final class MobileDay_Tests: XCTestCase { func testButton() { let button = Button( action: {}, label: { Label("Vitamin Play", systemImage: "figure.skating") } ).buttonStyle(VitaminButtonStyle(theme: .legacy(.primary))) .fixedSize() .padding() assertSnapshot(matching: button, as: .image) } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  4. import Foundation import SnapshotTesting import XCTest import SwiftUI @testable import

    VitaminPlay final class MobileDay_Tests: XCTestCase { func testButton() { let button = Button( action: {}, label: { Label("Vitamin Play", systemImage: "figure.skating") } ).buttonStyle(VitaminButtonStyle(theme: .legacy(.primary))) .fixedSize() .padding() assertSnapshot(matching: button, as: .image) } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  5. import Foundation import SnapshotTesting import XCTest import SwiftUI @testable import

    VitaminPlay final class MobileDay_Tests: XCTestCase { func testButton() { let button = Button( action: {}, label: { Label("Vitamin Play", systemImage: "figure.skating") } ).buttonStyle(VitaminButtonStyle(theme: .legacy(.primary))) .fixedSize() .padding() assertSnapshot(matching: button, as: .image) } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  6. import Foundation import SnapshotTesting import XCTest import SwiftUI @testable import

    VitaminPlay final class MobileDay_Tests: XCTestCase { func testButton() { let button = Button( action: {}, label: { Label("Vitamin Play", systemImage: "figure.skating") } ).buttonStyle(VitaminButtonStyle(theme: .legacy(.primary))) .fixedSize() .padding() assertSnapshot(matching: button, as: .image) } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  7. import Foundation import SnapshotTesting import XCTest import SwiftUI @testable import

    VitaminPlay final class MobileDay_Tests: XCTestCase { func testButton() { let button = Button( action: {}, label: { Label("Vitamin Play", systemImage: "figure.skating") } ).buttonStyle(VitaminButtonStyle(theme: .legacy(.primary))) .fixedSize() .padding() assertSnapshot(matching: button, as: .image) assertSnapshot(matching: button, as: .image(traits: .init(userInterfaceStyle: .light))) assertSnapshot(matching: button, as: .image(layout: .fixed(width: 200.0, height: 100.0), traits: .init(userInterfaceStyle: .light)), named: "fixed") assertSnapshot(matching: button, as: .image(layout: .device(config: .iPhoneSe), traits: .init(userInterfaceStyle: .light)), named: "device") } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  8. import Foundation import SnapshotTesting import XCTest import SwiftUI @testable import

    VitaminPlay final class MobileDay_Tests: XCTestCase { func testButton() { let button = Button( action: {}, label: { Label("Vitamin Play", systemImage: "figure.skating") } ).buttonStyle(VitaminButtonStyle(theme: .legacy(.primary))) .fixedSize() .padding() assertSnapshot(matching: button, as: .image) } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  9. On m'a demandé de faire des modifications sur le variant

    "secondary" du bouton en y ajoutant une jolie bordure rouge. Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  10. testButton(): failed - Snapshot does not match reference. @− ".../__Snapshots__/VitaminToggleStyleTests/testButton.1.png"

    @+ ".../tmp/VitaminToggleStyleTests/testButton.1.png" Newly-taken snapshot does not match reference. Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  11. Utilisation du SnapshotTesting avec Xcode Preview Jeffrey MACKO Tech Lead

    iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  12. Utilisation du SnapshotTesting avec Xcode Preview • Comment configurer les

    previews pour qu'elles soient snapshottables. • Comment créer des tests à partir des previews Xcode. Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  13. import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var previews: some

    View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configurations: [ .init(name: "Legacy Default", state: .legacy(.default)), .init(name: "Legacy Accent", state: .legacy(.accent)), .init(name: "Legacy Brand", state: .legacy(.brand)), .init(name: "Legacy Reversed", state: .legacy(.reversed)), .init(name: "Legacy Alert", state: .legacy(.alert)), ], configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(0, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(nil, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(6, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  14. import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var previews: some

    View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configurations: [ .init(name: "Legacy Default", state: .legacy(.default)), .init(name: "Legacy Accent", state: .legacy(.accent)), .init(name: "Legacy Brand", state: .legacy(.brand)), .init(name: "Legacy Reversed", state: .legacy(.reversed)), .init(name: "Legacy Alert", state: .legacy(.alert)), ], configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(0, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(nil, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(6, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  15. import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var previews: some

    View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configurations: [ .init(name: "Legacy Default", state: .legacy(.default)), .init(name: "Legacy Accent", state: .legacy(.accent)), .init(name: "Legacy Brand", state: .legacy(.brand)), .init(name: "Legacy Reversed", state: .legacy(.reversed)), .init(name: "Legacy Alert", state: .legacy(.alert)), ], configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(0, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(nil, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(6, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  16. import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var previews: some

    View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configurations: [ .init(name: "Legacy Default", state: .legacy(.default)), .init(name: "Legacy Accent", state: .legacy(.accent)), .init(name: "Legacy Brand", state: .legacy(.brand)), .init(name: "Legacy Reversed", state: .legacy(.reversed)), .init(name: "Legacy Alert", state: .legacy(.alert)), ], configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(0, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(nil, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(6, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  17. import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var previews: some

    View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configurations: [ .init(name: "Legacy Default", state: .legacy(.default)), .init(name: "Legacy Accent", state: .legacy(.accent)), .init(name: "Legacy Brand", state: .legacy(.brand)), .init(name: "Legacy Reversed", state: .legacy(.reversed)), .init(name: "Legacy Alert", state: .legacy(.alert)), ], configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(0, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(nil, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(6, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  18. import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var previews: some

    View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configurations: [ .init(name: "Legacy Default", state: .legacy(.default)), .init(name: "Legacy Accent", state: .legacy(.accent)), .init(name: "Legacy Brand", state: .legacy(.brand)), .init(name: "Legacy Reversed", state: .legacy(.reversed)), .init(name: "Legacy Alert", state: .legacy(.alert)), ], configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(0, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(nil, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(6, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  19. import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var previews: some

    View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configurations: [ .init(name: "Legacy Default", state: .legacy(.default)), .init(name: "Legacy Accent", state: .legacy(.accent)), .init(name: "Legacy Brand", state: .legacy(.brand)), .init(name: "Legacy Reversed", state: .legacy(.reversed)), .init(name: "Legacy Alert", state: .legacy(.alert)), ], configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(0, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(nil, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(6, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  20. import Foundation import PreviewSnapshotsTesting import XCTest @testable import VitaminPlay final

    class VitaminBadge_Tests: XCTestCase { func testPreviewsSnapshots() { VitaminBadge_Previews.snapshots.assertSnapshots() } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  21. import Foundation import PreviewSnapshotsTesting import XCTest @testable import VitaminPlay final

    class VitaminBadge_Tests: XCTestCase { func testPreviewsSnapshots() { VitaminBadge_Previews.snapshots.assertSnapshots() } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  22. import Foundation import PreviewSnapshotsTesting import XCTest @testable import VitaminPlay final

    class VitaminBadge_Tests: XCTestCase { func testPreviewsSnapshots() { VitaminBadge_Previews.snapshots.assertSnapshots() } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  23. Et si on rajoutait de la variabilité au niveau de

    l'environnement ? Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  24. import EnvironmentVariationPreview import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var

    previews: some View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() .environmentVariations([ .colorScheme(.dark), .colorScheme(.light), .layoutDirection(.rightToLeft), .legibilityWeight(.bold), .dynamicTypeSize(.xSmall), .dynamicTypeSize(.xxxLarge), ]) }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  25. import EnvironmentVariationPreview import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var

    previews: some View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() .environmentVariations([ .colorScheme(.dark), .colorScheme(.light), .layoutDirection(.rightToLeft), .legibilityWeight(.bold), .dynamicTypeSize(.xSmall), .dynamicTypeSize(.xxxLarge), ]) }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  26. import EnvironmentVariationPreview import PreviewSnapshots struct VitaminBadge_Previews: PreviewProvider { static var

    previews: some View { snapshots.previews.previewLayout(.sizeThatFits) } static var snapshots: PreviewSnapshots<VitaminBadge<Text>.Theme> { PreviewSnapshots( configurations: [ .init(name: "Legacy Default", state: .legacy(.default)), .init(name: "Legacy Accent", state: .legacy(.accent)), .init(name: "Legacy Brand", state: .legacy(.brand)), .init(name: "Legacy Reversed", state: .legacy(.reversed)), .init(name: "Legacy Alert", state: .legacy(.alert)), ], configure: { state in HStack { VStack { Label("Vitamin", systemImage: "basketball") .vitaminBadge(0, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(nil, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(6, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(99, badgeTheme: state) Label("Vitamin", systemImage: "basketball") .vitaminBadge(100, badgeTheme: state) } .frame(height: 120) .padding() .environmentVariations(vitaminConfiguration) }.padding() } ) } } Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  27. Code Coverage La cerise sur le ! , c'est que

    cela augmente drastiquement la couverture du code. Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  28. Avantages du SnapshotTesting par rapport aux tests d'interface utilisateur traditionnels

    Je ne vais pas comparer cette technique avec d'autres techniques de test d'interface utilisateur classiques, car je pense qu'elle est simple à mettre en place et à maintenir. Je la recommande donc en complément de vos tests d'interface utilisateur traditionnels. Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  29. Conclusion • SnapshotTesting est simple, rapide et efficace. • Il

    est flexible car il se base sur les previews pour visualiser une grande variété de rendus. • En ajoutant des environnements différents, cela permet d'avoir plus de variations dans les rendus. • Cela permet d'augmenter facilement la couverture de tests. Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  30. Devenez un core contributeur iOS • Participer à l'architecture du

    nouveau Vitamin iOS • Réalisation de composants et d'outils pour l'ensemble de Decathlon en SwiftUI • L'accessibilité et le support du RTL est clé pour nous • Aider à améliorer les tests et la documentation Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  31. Questions ? Jeffrey MACKO Tech Lead iOS Vitamin - Mobile

    Days 25/04 - Decathlon | Technology
  32. Links • https://gist.github.com/mackoj/ 7e28cb8aed69a4bf105499eb33204c5c • https://doordash.engineering/2023/01/18/how-to-speed-up- swiftui-development-and-testing-using-previewsnapshots/ • https://github.com/pointfreeco/swift-snapshot-testing •

    https://github.com/mackoj/EnvironmentVariationPreview • https://wiki.decathlon.net/pages/viewpage.action? Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology
  33. Comment sauvegarder les changements ? Il suffit de le déclarer

    avant l'assertSnapshots, afin de forcer le remplacement de la référence en local. SnapshotTesting.isRecording = true Jeffrey MACKO Tech Lead iOS Vitamin - Mobile Days 25/04 - Decathlon | Technology