Tests unitaires dans l'architecture Clean Swift

Bonjour lecteur!


Ce n'est un secret pour personne que les tests font partie intégrante de tout développement. Dans les articles précédents, nous avons couvert l'architecture de base de l'architecture Clean Swift , et il est maintenant temps d'apprendre à couvrir son unité avec des tests. Nous prendrons le projet de l'article sur les travailleurs comme base et analyserons les points principaux.



Théorie


GrĂące Ă  l' injection de dĂ©pendances et au protocole , tous les composants de scĂšne de Clean Swift sont indĂ©pendants les uns des autres et peuvent ĂȘtre testĂ©s sĂ©parĂ©ment. Par exemple, Interactor dĂ©pend de Presenter et Worker , mais ces dĂ©pendances sont facultatives et basĂ©es sur le protocole. Ainsi, Interactor peut effectuer son travail (bien que infĂ©rieur) sans Presenter'a et Worker'a , et nous pouvons Ă©galement les remplacer par d'autres objets signĂ©s selon leurs protocoles.


Puisque nous voulons tester chaque composant séparément, nous devons remplacer les dépendances par des pseudo-composants . Cela nous aidera à espionner (Spy). Les espions sont des objets de test qui implémentent les protocoles que nous voulons injecter et suivre les appels de méthode qu'ils contiennent. En d'autres termes, nous créons Spy for Presenter et Worker , puis les injectons dans Interactor pour suivre les appels de méthode.



Pour ĂȘtre honnĂȘte, j'ajouterai qu'il existe Ă©galement des objets de test ( Test Doubles ) Dummy , Fake , Stub et Mock . Mais dans le cadre de cet article, nous ne les toucherons pas. En savoir plus ici - TestDoubles


Pratique


TerminĂ© avec les mots, passons aux choses sĂ©rieuses. Pour gagner du temps, nous considĂ©rerons le code abstrait sans entrer dans les dĂ©tails de l'implĂ©mentation de chaque mĂ©thode. Les dĂ©tails du code d'application peuvent ĂȘtre trouvĂ©s ici: CleanSwiftTests


Pour chaque composant de la scÚne, nous créons un fichier avec des tests et des tests doubles pour les dépendances des composants ( Test Doubles ).


Un exemple d'une telle structure:



La structure de chaque fichier de test (pour les composants) est identique et respecte approximativement la séquence d'écriture suivante:


  • Nous dĂ©clarons une variable avec SUT (l'objet que nous allons tester) et des variables avec ses principales dĂ©pendances
  • Nous initialisons le SUT et ses dĂ©pendances dans setUp () , puis les effaçons dans tearDown ()
  • MĂ©thodes d'essai

Comme nous l'avons discutĂ© en thĂ©orie, chaque composant de la scĂšne peut ĂȘtre testĂ© sĂ©parĂ©ment. Nous pouvons injecter des doublons de test ( Spy ) dans ses dĂ©pendances et ainsi surveiller le fonctionnement des mĂ©thodes de notre SUT . Examinons de plus prĂšs le processus d'Ă©criture des tests Ă  l'aide de l'exemple Interactor de la scĂšne d' accueil .


HomeInteractor dépend de deux objets - Presenter et Worker . Les deux variables de la classe ont un type de protocole. Cela signifie que nous pouvons créer des doublons de test signés sous les protocoles HomePresentationLogic et HomeWorkingLogic , puis les injecter dans HomeInteractor .


final class HomeInteractor: HomeBusinessLogic, HomeDataStore { // MARK: - Public Properties var presenter: HomePresentationLogic? lazy var worker: HomeWorkingLogic = HomeWorker() var users: [User] = [] var selectedUser: User? // MARK: - HomeBusinessLogic func fetchUsers(_ request: HomeModels.FetchUsers.Request) { // ... } func selectUser(_ request: HomeModels.SelectUser.Request) { // ... } 

Nous testerons deux méthodes:


  • fetchUsers (:) . Responsable d'obtenir une liste d'utilisateurs par API . Une demande d' API est envoyĂ©e Ă  l'aide de Worker .
  • selectUser (:) . Responsable de la sĂ©lection de l'utilisateur actif ( selectedUser ) dans la liste des utilisateurs chargĂ©s ( utilisateurs ).

Pour commencer à écrire les tests d'Interactor , nous devons créer des espions qui suivront l'invocation des méthodes dans HomePresentationLogic et HomeWorkingLogic . Pour ce faire, créez la classe HomePresentationLogicSpy dans le répertoire 'CleanSwiftTestsTests / Stores / Home / TestDoubles / Spies', signez le protocole HomePresentationLogic et implémentez la méthode de ce protocole.


 final class HomePresentationLogicSpy: HomePresentationLogic { // MARK: - Public Properties private(set) var isCalledPresentFetchedUsers = false // MARK: - Public Methods func presentFetchedUsers(_ response: HomeModels.FetchUsers.Response) { isCalledPresentFetchedUsers = true } } 

Tout est extrĂȘmement transparent ici. Si la mĂ©thode presentFetchedUsers ( HomePresentationLogic ) a Ă©tĂ© appelĂ©e, nous dĂ©finissons la valeur de la variable isCalledPresentFetchedUsers sur true . Ainsi, nous pouvons suivre si cette mĂ©thode a Ă©tĂ© appelĂ©e lors du test d' Interactor .


En utilisant le mĂȘme principe, crĂ©ez HomeWorkingLogicSpy . Une diffĂ©rence, nous appelons l' achĂšvement , car une partie du code dans Interactor sera encapsulĂ©e dans la fermeture de cette mĂ©thode. Les mĂ©thodes HomeWorkingLogic traitent les requĂȘtes rĂ©seau. Nous devons Ă©viter de rĂ©elles demandes de rĂ©seau lors des tests. Pour ce faire, nous le remplaçons par une prise de test, qui suit les appels de mĂ©thode et renvoie les donnĂ©es du modĂšle, mais ne fait aucune demande au rĂ©seau.


 final class HomeWorkingLogicSpy: HomeWorkingLogic { // MARK: - Public Properties private(set) var isCalledFetchUsers = false let users: [User] = [ User(id: 1, name: "Ivan", username: "ivan91"), User(id: 2, name: "Igor", username: "igor_test") ] // MARK: - Public Methods func fetchUsers(_ completion: @escaping ([User]?) -> Void) { isCalledFetchUsers = true completion(users) } } 

Ensuite, nous créons la classe HomeInteractorTests , avec laquelle nous testerons HomeInteractor .


 final class HomeInteractorTests: XCTestCase { // MARK: - Private Properties private var sut: HomeInteractor! private var worker: HomeWorkingLogicSpy! private var presenter: HomePresentationLogicSpy! // MARK: - Lifecycle override func setUp() { super.setUp() let homeInteractor = HomeInteractor() let homeWorker = HomeWorkingLogicSpy() let homePresenter = HomePresentationLogicSpy() homeInteractor.worker = homeWorker homeInteractor.presenter = homePresenter sut = homeInteractor worker = homeWorker presenter = homePresenter } override func tearDown() { sut = nil worker = nil presenter = nil super.tearDown() } // MARK: - Public Methods func testFetchUsers() { // ... } func testSelectUser() { // ... } } 

Nous indiquons trois variables principales - sut , travailleur et présentateur .


Dans setUp (), initialisez les objets nécessaires, injectez des dépendances dans Interactor et affectez des objets aux variables de classe.
Dans tearDown (), nous effaçons les variables de classe pour la pureté de l'expérience.


La méthode setUp () est appelée avant le début de la méthode de test, par exemple testFetchUsers () et tearDown () lorsque cette méthode est terminée. Ainsi, nous recréons l'objet de test ( sut ) avant chaque exécution de la méthode de test.


Viennent ensuite les mĂ©thodes de test elles-mĂȘmes. La structure est divisĂ©e en 3 blocs logiques principaux - la crĂ©ation des objets nĂ©cessaires, le lancement de la mĂ©thode testĂ©e en SUT et la vĂ©rification des rĂ©sultats. Dans l'exemple ci-dessous, nous crĂ©ons une demande (dans notre cas, elle n'a pas de paramĂštres), exĂ©cutons la mĂ©thode fetchUsers (:) Interactor'a , puis vĂ©rifions si les mĂ©thodes nĂ©cessaires ont Ă©tĂ© appelĂ©es dans HomeWorkingLogicSpy et HomePresentationLogicSpy . Nous vĂ©rifions Ă©galement si Interactor a enregistrĂ© les donnĂ©es de test reçues de Worker dans son DataStore .


 func testFetchUsers() { let request = HomeModels.FetchUsers.Request() sut.fetchUsers(request) XCTAssertTrue(worker.isCalledFetchUsers, "Not started worker.fetchUsers(:)") XCTAssertTrue(presenter.isCalledPresentFetchedUsers, "Not started presenter.presentFetchedUsers(:)") XCTAssertEqual(sut.users.count, worker.users.count) } 

Nous testerons le choix de l'utilisateur par une structure similaire. Nous déclarons les variables expectationId et expectationName , par lesquelles nous comparerons le résultat de la sélection de l'utilisateur. La variable utilisateurs stocke une liste de tests d'utilisateurs que nous affectons à Interactor . Parce que les méthodes de test sont appelées indépendamment les unes des autres, et dans tearDown () nous mettons à zéro les données, puis la liste des utilisateurs d' Interactor est vide et nous devons la remplir avec quelque chose. Et puis nous vérifions si l'utilisateur a été affecté dans DataStore Interactor'a , aprÚs avoir appelé sut.selectUser (:) , et si l'utilisateur est le bon.


 func testSelectUser() { let expectationId = 2 let expectationName = "Vasya" let users = [ User(id: 1, name: "Ivan", username: "ivan"), User(id: 2, name: "Vasya", username: "vasya91"), User(id: 3, name: "Maria", username: "maria_love") ] let request = HomeModels.SelectUser.Request(index: 1) sut.users = users sut.selectUser(request) XCTAssertNotNil(sut.selectedUser, "User not selected") XCTAssertEqual(sut.selectedUser?.id, expectationId) XCTAssertEqual(sut.selectedUser?.name, expectationName) } 

Les tests Presenter'a et ViewController'a se font sur le mĂȘme principe, avec des diffĂ©rences minimes. L'une des diffĂ©rences est que pour tester le ViewController, vous devrez crĂ©er une UIWindow et obtenir le contrĂŽleur du Storyboard dans setUp () , ainsi que crĂ©er des objets Spy sur les tables et les collections. Mais ces nuances varient selon les besoins.


Pour ĂȘtre complet, je vous recommande de vous familiariser avec le projet par le lien Ă  la fin de l'article.


Conclusion


Nous avons couvert les principes de base des tests d'applications sur l'architecture Clean Swift . Il ne prĂ©sente pas de diffĂ©rences fondamentalement fortes par rapport aux projets de test sur d'autres architectures, tout de mĂȘme les tests en double, l'injection et les protocoles. L'essentiel est de ne pas oublier que chaque cycle VIP doit avoir une (et une seule!) ResponsabilitĂ©. Cela rendra le code plus propre et les tests plus Ă©vidents.


Lien vers le projet: CleanSwiftTests
Aide à la rédaction d'un article: Bastien


SĂ©rie d'articles


  1. Présentation de Clean Swift Architecture
  2. Routeur et transmission de données dans une architecture propre et rapide
  3. Travailleurs dans une architecture propre et rapide
  4. Tests unitaires dans l'architecture Clean Swift (vous ĂȘtes ici)
  5. Un exemple d'une architecture de boutique en ligne simple Clean Swift

Source: https://habr.com/ru/post/fr483882/


All Articles