Déroulement des tests

De Traduction du Wiki diaspora* (non officiel)
Aller à la navigation Aller à la recherche

Afin d'assurer la qualité du code, de faciliter le remaniement et de s'assurer que seul le code réellement nécessaire est écrit, nous nous efforçons d'encourager le TDD (Développements Pilotés par les Tests) ou le BDD (Programmation pilotée par le comportement) lorsque cela est possible. Il y a une énorme liste d'avantages qui résultent de ce style de développement et même quelques recherches à ce sujet référencées dans les articles de Wikipedia, si vous êtes intéressé par la lecture de ce sujet.

flux de développement piloté par les tests

En substance, le TDD signifie qu'avant d'écrire du code réel, vous êtes censé écrire d'abord un testcase, en utilisant la fonctionnalité ou la fonction de la manière dont elle devrait se présenter à l'extérieur. Bien sûr, comme aucun code réel n'a encore été écrit, le testcase échouera s'il est exécuté. Maintenant, l'idée est d'écrire seulement la quantité minimale de code qui permet au testcase de passer et, dès qu'il le fait, de remanier le code jusqu'à ce que vous vous retrouviez avec un code parfaitement structuré et modulaire, qui passe même votre super-suite de tests.

Cela peut se faire sur plusieurs niveaux, depuis les fonctions, méthodes et classes individuelles jusqu'à ce qui est présenté à l'utilisateur dans l'interface utilisateur. Il existe différents outils pour chacune de ces couches.

Préparations générales

Certains tests nécessitent l'existence de pages d'erreur statiques, générez-les avec

bin/rake assets:generate_error_pages

Rspec

Puisque Diaspora est basé sur Ruby on Rails, nous obtenons l'environnement de test rspec pratiquement gratuitement. Nos rspec tests sont situés dans le répertoire spec/ et ce répertoire est divisé en sous-répertoires différenciant les parties du code qui sont testées avec les fichiers qui les contiennent (par exemple, les modèles dans spec/models/, les contrôleurs dans spec/controllers/ ou la lib Diaspora dans spec/lib/).

Prenons un exemple : Si nous devions tester les règles de validation et de nettoyage du modèle Profil, l'endroit où les chercher est spec/models/profile_spec.rb. À l'intérieur de ce fichier, vous verrez que les tests sont écrits en code ruby simple structuré par un DSL (Langage dédié), dans ce cas quelques méthodes ruby utiles qui nous permettent d'imbriquer les tests logiquement et de vérifier les résultats plus facilement.

describe Profile do
  describe 'validation' do
    describe "of last_name" do
      it "strips leading and trailing whitespace" do
        profile = Factory.build(:profile, :last_name => " Ohba ")
        profile.should be_valid
        profile.last_name.should == "Ohba"
      end
    end
  end
end

Cela nous montre que la description des tests est censée être facilement lisible par l'homme, car c'est également la partie qui s'affiche, lorsque le test échoue ("validation" "du nom de famille" " supprime les espaces blancs de début et de fin"). Une autre chose qui devrait attirer votre attention dans cet exemple est l'utilisation d'une factory pour créer des objets fantaisie à tester. Cela réduit considérablement la quantité de travail nécessaire à la création de tests quelque peu réalistes.

Avant de lancer les tests pour la première fois, assurez-vous d'avoir créé le schéma de la base de données pour l'environnement `test` en faisant ;

RAILS_ENV="test" bin/rake db:create db:migrate

Si vous avez trop de temps ou si vous voulez vous assurer que vous n'avez rien cassé en dehors de ce sur quoi vous travailliez, vous pouvez exécuter toutes les tâches rspec

bin/rake spec

Pour uniquement exécuter le fichier de spécifications de notre profil exemple

bin/rspec spec/models/profile_spec.rb

Si votre test échoue, la sortie de la commande vous affichera ceci

=> Building fixtures
=> Built aspect_memberships.yml, aspects.yml, contacts.yml, people.yml, profiles.yml, and users.yml
Run options: exclude {:performance=>true}

  1) Profile validation of first_name fails on purpose
     Failure/Error: false.should == true
     expected: true
     got: false (using ==)
     # ./spec/models/profile_spec.rb:11:in `block (4 levels) in <top (required)>'

  47/47:       100% |==========================================================================| Time: 00:00:00

Finished in 0.95665 seconds
47 examples, 1 failure

Failed examples:

rspec ./spec/models/profile_spec.rb:10 # Profile validation of first_name fails on purpose

Randomized with seed 28937

Si tous vos tests sont bons, il vous affichera un résumé et quelques statistiques similaires à ce qui suit

=> Building fixtures
=> Built aspect_memberships.yml, aspects.yml, contacts.yml, people.yml, profiles.yml, and users.yml
Run options: exclude {:performance=>true}
  46/46:       100% |==========================================================================| Time: 00:00:00

Top 10 slowest examples:
  Profile tags strips more than 5 tags
    0.14156 seconds ./spec/models/profile_spec.rb:258
  Profile validation of first_name can be 32 characters long
    0.07987 seconds ./spec/models/profile_spec.rb:16
  Profile serialization includes tags
    0.05348 seconds ./spec/models/profile_spec.rb:157
  Profile tags it should behave like it is taggable.format_tags doesn't mangle text when tags are involved
    0.05271 seconds ./spec/shared_behaviors/taggable.rb:42
  Profile tags it should behave like it is taggable.format_tags supports non-ascii characters
    0.05078 seconds ./spec/shared_behaviors/taggable.rb:27
  Profile tags it should behave like it is taggable.format_tags responds to plain_text
    0.04862 seconds ./spec/shared_behaviors/taggable.rb:38
  Profile tags it should behave like it is taggable.format_tags links each tag
    0.04851 seconds ./spec/shared_behaviors/taggable.rb:31
  Profile tags it should behave like it is taggable#build_tags builds the tags
    0.04171 seconds ./spec/shared_behaviors/taggable.rb:91
  Profile serialization includes location
    0.02894 seconds ./spec/models/profile_spec.rb:165
  Profile validation#contruct_full_name generates a full name given only first name
    0.02722 seconds ./spec/models/profile_spec.rb:56

Finished in 0.94219 seconds
46 examples, 0 failures

Randomized with seed 16731

Il peut être important de savoir qu'il faut toujours exécuter les tests rspec en premier si vous prévoyez d'utiliser l'un des autres tests, car certains cas rspec génèrent des fixtures (snippets hmtl/json) qui sont nécessaires pour que les autres tests fonctionnent.

Jasmine

Les tests Jasmine pour JavaScript sont situés à l'intérieur du répertoire spec/javascripts/. Ils sont répartis de manière similaire à la façon dont les fichiers JavaScript sont structurés dans app/assets/javascripts/.

Les tests Jasmine tentent de ressembler aux tests rspec à la fois dans leur structure et dans la nomenclature des méthodes d'aide disponibles, et nous utilisons également des factories ici. Si vous n'avez jamais travaillé avec Jasmine, vous pouvez lire cette belle introduction pour avoir une idée générale de ce dont il s'agit et de ce à quoi cela doit servir.

Remarque ! Les tests Jasmine utilisent certaines fixtures générées par les tests rspec, donc si vous n'avez pas encore exécuté ces derniers, générez d'abord les fixtures avec la commande suivante :

bin/rake tests:generate_fixtures

Au cas où la commande ci-dessus échoue, essayez de recréer la base de données de test : RAILS_ENV=test bin/rake db:drop db:create db:migrate

Le code côté client de Diaspora utilise Backbone.js et Handlebars templating system. Cela nous donne une approche basée sur les modèles et les vues pour rendre de nombreux éléments du même type dans le navigateur, tout en soulageant le serveur puisqu'il n'a plus à effectuer de rendu significatif. Jetons un coup d'œil à un test Jasmine pour le modèle JavaScript User, situé dans spec/javascripts/app/models/user_spec.js.

describe("app.models.User", function(){
  beforeEach(function(){
    this.user = new app.models.User({});
  }); 

  describe("authenticated", function(){
    it("should be true if ID is nil", function(){
      expect(this.user.authenticated()).toBeFalsy();
    }); 

    it('should be true if ID is set', function(){
      this.user.set({id : 1});
      expect(this.user.authenticated()).toBeTruthy();
    }); 
  }); 

  describe("isServiceConnected", function(){
    it("checks to see if the sent provider name is a configured service", function(){
      this.user.set({configured_services : ["facebook"]});
      expect(this.user.isServiceConfigured("facebook")).toBeTruthy();
      expect(this.user.isServiceConfigured("tumblr")).toBeFalsy();
    }); 
  }); 
});

Vous pouvez le voir, c'est assez similaire à rspec, la seule différence majeure étant la syntaxe JavaScript. Il est facile de reconnaître le nommage compréhensible par un humain du testcase et les fonctions spéciales qui évaluent le résultat du test (expect(...)).

Les tests Jasmine sont exécutés dans le navigateur, puisqu'ils nécessitent un environnement JavaScript, bien que pour CI (Intégration continue (Continuous Integration) ils puissent également être exécutés en ligne de commande, ce qui nécessite un tampon d'image virtuel à la place d'un écran réel. Le serveur web de Jasmine est lancé par

RAILS_ENV=test bin/rake jasmine

Qui, une fois terminé, vous dira, qu'il est joignable sur http://localhost:8888/

your server is running here: http://localhost:8888/
[...]

[2017-09-03 17:35:29] INFO  WEBrick 1.3.1
[2017-09-03 17:35:29] INFO  ruby 2.4.1 (2017-03-22) [x86_64-linux]
[2017-09-03 17:35:29] INFO  WEBrick::HTTPServer#start: pid=16270 port=8888

Si vous visitez cette page dans votre navigateur, et que tous les tests sont réussis, elle devrait ressembler à ceci

jasmine tests

Si vous souhaitez afficher uniquement les tests d'un fichier, vous ne devez pas spécifier le chemin d'accès sur la ligne de commande comme c'était le cas avec rspec. Au lieu de cela, fournissez un paramètre supplémentaire à l'URL dans votre navigateur. Par exemple, http://localhost:8888/?spec=app.models.User
. Ajoutez un test qui échoue et vous obtiendrez ce qui suit

jasmine test défaillant

Vous pouvez laisser le serveur Web démarré par Jasmine fonctionner en arrière-plan pendant que vous travaillez sur votre code ou sur le test, et appuyer sur l'actualisation dans le navigateur pour voir si quelque chose a changé une fois que vous avez fini d'éditer.

Pour exécuter tous les tests jasmine à partir de la ligne de commande, exécutez la commande suivante :

bin/rake jasmine:ci

Cucumber

Warning.png »» Important
Avant d'exécuter des tests Cucumber, assurez-vous que votre version PhantomJS est au moins 2.1.


Les tests Cucumber ou cukes sont les plus éloignés des détails de mise en œuvre et des problèmes de code réels. Ils ne font que décrire un aspect comportemental du système et le tester. Ce fait devient très clair si vous jetez un coup d'œil à l'un des fichiers du dossier features/. Les cas de test sont écrits en anglais simple, d'une manière que même la personne la plus réfractaire à la technologie dans le monde entier pourrait comprendre.

Examinons la fonctionnalité Photos dans features/desktop/photos.feature.

@javascript
Feature: photos
    In order to enlighten humanity for the good of society
    As a rock star
    I want to post pictures of a button

  Background:
      Given a user with username "bob"
      When I sign in as "bob@bob.bob"

      And I am on the home page

  Scenario: deleting a photo will delete a photo-only post if the photo was the last image
    Given I expand the publisher
    And I attach the file "spec/fixtures/button.png" to hidden element "file" within "#file-upload"
    And I wait for the ajax to finish
    And I press "Share"
    And I wait for the ajax to finish

    When I go to the photo page for "bob@bob.bob"'s latest post
    And I follow "edit_photo_toggle"
    And I preemptively confirm the alert
    And I press "Delete Photo"
    And I go to the home page

    Then I should not see any posts in my stream

Bien sûr, cela ne peut pas être compris par programme informatique lui-même. C'est pourquoi il doit y avoir un tas de code derrière pour que cela fonctionne réellement. C'est écrit en Ruby à l'aide d'expressions régulières. Par exemple, dans features/step_definitions/posts_steps.rb, on peut lire ceci

Then /^I should not see an uploaded image within the photo drop zone$/ do
  all("#photodropzone img").should be_empty
end

Then /^I should not see any posts in my stream$/ do
  all(".stream_element").should be_empty
end

Given /^"([^"]*)" has a public post with text "([^"]*)"$/ do |email, text|
  user = User.find_by_email(email)
  user.post(:status_message, :text => text, :public => true, :to => user.aspects)
end

Given /^"([^"]*)" has a non public post with text "([^"]*)"$/ do |email, text|
  user = User.find_by_email(email)
  user.post(:status_message, :text => text, :public => false, :to => user.aspects)
end

When /^The user deletes their first post$/ do
  @me.posts.first.destroy
end

When /^I click on the first block button/ do
  find(".block_user").click
end

La meilleure façon de commencer avec Cucumber - comme avec tout autre système de test - serait probablement de copier et coller des tests existants qui font quelque chose de similaire à ce que vous essayez d'obtenir et de les adapter à votre cas spécifique. Cucumber dispose d'un wiki d'information, qui vous aidera à démarrer.

L'exécution de toutes les fonctionnalités de Cucumber est initiée par

bin/rake cucumber

Cela prendra un peu de temps pour démarrer, mais vous pourrez ensuite observer votre navigateur s'ouvrir et le terminal enregistrer tout ce qui est exécuté, ce qui prend environ 7 à 21 minutes (selon votre machine). Si vous ne voulez exécuter qu'une seule fonctionnalité, vous pouvez exécuter

bin/cucumber features/desktop/photos.feature

Cela donnera quelque chose de similaire à

Using the default profile...
...................................................

3 scenarios (3 passed)
48 steps (48 passed)
0m30.300s

Débogage

Comme les tests cucumber sont exécutés dans phantomJS donc sans ouverture d'un vrai navigateur, vous ne pouvez pas les voir s'exécuter. Cela rend les tests plus difficiles à déboguer. C'est pourquoi nous avons introduit la variable d'environnement SCREENSHOT_PATH. Lancez les tests en définissant cette variable pour avoir une capture d'écran de tous les scénarios en échec au moment où ils ont échoué. Ainsi, par exemple, la ligne de commande ci-dessus enregistrera les captures d'écran des scénarios en échec dans le dossier d'installation de diaspora.

SCREENSHOT_PATH="." bin/cucumber features/desktop/change_settings.feature

Guard

Vous pouvez exécuter automatiquement les tests concernés au fur et à mesure que vous modifiez les fichiers, en laissant simplement guard fonctionner en arrière-plan :

bin/guard

Dépannage du lancement des suites de tests

J'obtiens des tonnes d'erreurs de PostgreSQL lors de l'exécution des specs

Si vous obtenez des erreurs qui ressemblent à ceci :

...
  4) PollParticipation it is relayable it should behave like it is relayable validations on :author_id the author is on the parent object author's ignore list when object is created works if the object has no parent
     Failure/Error: Unable to find matching line from backtrace
     ActiveRecord::StatementInvalid:
       PG::ConnectionBad: PQsocket() can't get socket descriptor: BEGIN
     Shared Example Group: "it is relayable" called from ./spec/models/poll_participation_spec.rb:125
     # /home/user/.rvm/gems/ruby-2.2.1@diaspora/gems/activerecord-4.2.3/lib/active_record/connection_adapters/postgresql/database_statements.rb:155:in `async_exec'
     # /home/user/.rvm/gems/ruby-2.2.1@diaspora/gems/activerecord-4.2.3/lib/active_record/connection_adapters/postgresql/database_statements.rb:155:in `block in execute'
     # /home/user/.rvm/gems/ruby-2.2.1@diaspora/gems/activerecord-4.2.3/lib/active_record/connection_adapters/abstract_adapter.rb:473:in `block in log'
...

alors vous pouvez, dans les configurations de développement, essayer de désactiver SSL pour PostgreSQL dans son fichier de configuration. Par exemple, dans /etc/postgresql/9.4/main/postgresql.conf changer la ligne ssl = true en ssl = false et redémarrer PostgreSQL.

C'est tout

Si vous avez encore des questions sur la façon d'écrire ou d'utiliser les tests, rejoignez-nous sur IRC ou signalez votre problème sur le discourse.