Choses à faire

24h dans une journée, et tant de choses à faire !

Mocks & Stubs : La suite

publié le 22 octobre 2009 par Killian Ebel

Voici la suite de mon article précédent, « Leurre des tests » … Nous allons passer rapidement en revue les différents outils à notre disposition sur d’autres plate-formes pour exploiter les Mocks et les Stubs.

EasyMock (Java)

EasyMock propose d’englober l’interface pour créer votre Mock et d’effectuer les vérifications directement sur le Mock :

public void testMemberMailSentWhenSubscribed() {
 // Création d'un membre
 Member member = new Member("login", "password");
 Message msg = new Message("Bienvenue login !"); 

 // On met en place le Mock
 MailService mailer = createMock(MailService.class); 

 // On s'attend à ce que la méthode soit appelée avec comme paramètre le message msg
 expect(mailer.send(msg));
 replay(mailer); 

 MyApp.setMailer(mailer); 

 // On enregistre le membre
 MyApp.register(member);
 verify(mailer);
}

jMock (Java)

jMock fonctionne par création d’un contexte devant être satisfait. Je trouve la façon d’écrire les vérifications (partie Expectations) plutôt intéressante :

public void testMemberMailSentWhenSubscribed() {
 // Création d'un membre
 Member member = new Member("login", "password");
 Message msg = new Message("Bienvenue login !");

 // On met en place le Mock
 MailService mailer = context.mock(MailService.class); 

 // On définit les attentes
 context.checking(new Expectations() {{
 oneOf(mailer).send(msg);
 }}; 

 MyApp.setMailer(mailer); 

 // On enregistre le membre
 MyApp.register(member); 

 context.assertIsSatisfied();
}

Mockito (Java, Python)

Mockito permet de fonctionner en mode Mock ou en mode Stub, ce qui est intéressant selon le contexte. Voici la version Java du code :

public void testMemberMailSentWhenSubscribed() {
 // Création d'un membre
 Member member = new Member("login", "password");
 Message msg = new Message("Bienvenue login !");

 // On met en place le Mock
 MailService mailer = mock(MailService.class);
 MyApp.setMailer(mailer); 

 // On enregistre le membre
 MyApp.register(member); 

 // On vérifie l'appel
 verify(mailer).send(msg);
}

Et voici la version Python :

def test_member_mail_sent_when_subscribed(self):

 # Création d'un membre
 member = Member("login", "password")
 msg = Message("Bienvenue login !") 

 # On met en place le Mock
 mailer = Mock()
 MyApp.mailer = mailer 

 # On enregistre le membre
 MyApp.register(member) 

 # On vérifie l'appel
 verify(mailer).send(msg)

SevenMock (Java)

Ce framework m’a apparu assez souple mais très verbeux comparé à certains autres. Les vérifications passent par un contrôleur central :

public void testMemberMailSentWhenSubscribed() {
 // Création d'un membre
 Member member = new Member("login", "password");
 Message msg = new Message("Bienvenue login !");

 // On met en place le Mock
 MockController mockControl = new MockController();
 MailService mailer = mockControl.getMock(MailService.class);
 mockControl.expect(new MailService() {
 public void send(Message _msg) { // La méthode send doit être appelée
 assertEquals(msg, _msg); // On vérifie le paramètre
 }
 });

 MyApp.setMailer(mailer);

 // On enregistre le membre
 MyApp.register(member);

 // On vérifie l'appel
 mockControl.verify();
}

JMockit (Java)

JMockit utilise un concept intéressant en écrivant les attentes directement dans une classe Mock. La méthode de test sert à décrire les actions à exécuter, tandis que la classe de Mock va vérifier les paramètres, le nombre d’appels, etc… :

public void testMemberMailSentWhenSubscribed() {
 // Mise en place des Mocks
 Mockit.setUpMocks(MailServiceMock.class);

 // Création d'un membre
 Member member = new Member("login", "password");

 // On enregistre le membre
 MyApp.register(member);
}

@MockClass(realClass = MailService.class)
public static class MailServiceMock {
 @Mock(invocations = 1)
 public void send(Message msg) {
 assertEquals(new Message("Bienvenue login !"), msg);
 }
}

rMock (Java)

rMock enregistre les actions à partir du moment où startVerification() est appelée. J’ai eu un peu de mal avec ce framework que je trouve moins intuitif que la plupart des autres :

public void testMemberMailSentWhenSubscribed() {
 // Création d'un membre
 Member member = new Member("login", "password");
 Message msg = new Message("Bienvenue login !");

 // On met en place le Mock
 MailService mailer = (MailService) mock(MailService.class);
 mailer.send(msg);
 startVerification();

 MyApp.setMailer(mailer);

 // On enregistre le membre
 MyApp.register(member);
}

Unitils (Java)

Unitils est un framework très intéressant en ce qui concerne les tests des DAO (accès aux bases de données). Un prochain article étudiera les différentes façons de tester une application accédant à une base de données (bien que passer par les Mock soit souvent une meilleure pratique). Le framework n’est pas spécialisé dans les Mock objects mais sait travailler avec.

La procédure est la suivante, ajouter un champ à la classe de test, qui se chargera d’instancier le Mock elle-même, puis travailler avec des assertions sur les interactions :

Mock<MailService> mockMailer;

public void testMemberMailSentWhenSubscribed() {
 // Création d'un membre
 Member member = new Member("login", "password");
 Message msg = new Message("Bienvenue login !");

 // On met en place le Mock
 MyApp.setMailer(mockMailer.getMock());

 // On enregistre le membre
 MyApp.register(member);

 // On vérifie l'appel
 mockMailer.assertInvoked.send(msg);
}

Ludibrio (Python)

Ludibrio permet de prendre tous les « Test Doubles » (Mocks, Stubs et Spy, Fakes et Dummy Objects). La documentation est par contre assez pauvre et je ne suis pas sûr d’avoir bien compris le fonctionnement, voici tout de même un exemple de ce que j’ai pu comprendre :

def test_member_mail_sent_when_subscribed(self):
 # Création d'un membre
 member = Member("login", "password")
 msg = Message("Bienvenue login !")

 # On met en place le Mock
 with Mock() as mailer:
 mailer.send(msg) << 1

 MyApp.mailer = mailer

 # On enregistre le membre
 MyApp.register(member)

The Python Mock Module (Python)

Python étant un langage plus dynamique que Java, la façon d’utiliser les Mocks est à vrai dire totalement différente. Le Python Mock Module permet de rajouter dynamiquement des attentes sur la classe de Mock et lèvera une exception si l’attente n’est pas respectée :

def test_member_mail_sent_when_subscribed(self):
 # Création d'un membre
 member = Member("login", "password")
 msg = Message("Bienvenue login !")

 # On met en place le Mock
 mailer = Mock({}, MailService)
 mailer.mockSetExpectation('send', expectParam(0, IS(msg)))

 MyApp.mailer = mailer

 # On enregistre le membre
 MyApp.register(member)

PyMock (Python)

PyMock permet aux classes de test d’hériter du classique TestCase pour rester cohérent avec d’autres tests, ou alors d’hériter directement de PyMockTestCase pour simplifier l’utilisation des Mocks :

def test_member_mail_sent_when_subscribed(self):
 # Création d'un membre
 member = Member("login", "password")
 msg = Message("Bienvenue login !")

 # On met en place le Mock
 mailer = self.mock()
 mailer.send(msg)

 # Le replay indique qu'à partir de là on va vérifier que les appels précédents sont répétés
 self.replay()

 MyApp.mailer = mailer

 # On enregistre le membre
 MyApp.register(member)

 # Vérifie l'appel
 self.verify()

Mocker (Python)

Mocker fonctionne à peu près comme PyMock, mais je trouve que les possibilités sont plus grandes en terme d’assertions par exemple :

def test_member_mail_sent_when_subscribed(self):
    # Création d'un membre
    member = Member("login", "password")
    msg = Message("Bienvenue login !")

    # On met en place le Mock
    mailer = self.mocker.mock()
    mailer.send(msg)

    mocker.replay()
    MyApp.mailer = mailer

    # On enregistre le membre
    MyApp.register(member)

    # Vérifie l'appel
    self.mocker.restore()
    self.mocker.verify()

Pymox (Python)

J’aime beaucoup PyMox, que je trouve moins intrusif que les autres outils Python. Il permet d’utiliser une factory (Mox) ou d’instancier directement les objets Mock. Un usage avancé est possible, je vous laisse regarder la documentation. Voici l’utilisation en mode factory :

def test_member_mail_sent_when_subscribed(self):
    # Création d'un membre
    member = Member("login", "password")
    msg = Message("Bienvenue login !")

    # On met en place le Mock
    mocker = mox.Mox()
    mocker.CreateMock(MailService)

    mailer = self.mocker.mock()
    mailer.send(msg)

    mocker.ReplayAll()
    MyApp.mailer = mailer

    # On enregistre le membre
    MyApp.register(member)

    # Vérifie l'appel
    mocker.VerifyAll()

Google Mock (C++)

Le géant du web s’investit aussi dans les frameworks de Mocking (en plus du reste). Comme le C++ n’était pas fait pour ça à la base, ils ont fait un énorme travail en l’adaptant pour rendre l’écriture des tests assez simple et intuitive. Même si on reste très loin de la flexibilité du Python (C++ oblige), les possibilités sont énormes.

Voici notre interface MailService réécrite en C++ :

class MailService {
    public:
        virtual ~MailService();
        virtual void send(Message &message) = 0;
}

Le Mock Object, avec la syntaxe particulière de Google Mock :

class MailServiceMock : MailService {
    public:
        // Un argument de type Message&, retour de type void
        MOCK_METHOD1(send, void(Message &message));
}

Et le test :

TEST(MailServiceTest, MailSentWhenSubscribed) {
  Member member = new Member("login", "password");
  Message msg = new Message("Bienvenue login !");

  // On met en place le Mock
  MyApp.setMailer(&mailer);

  EXPECT_CALL(mailer, send(&msg))
      .Times(1);

  // On enregistre le membre
  MyApp.register(&member);

  // Libération de la mémoire
  delete member;
  ...
}   

int main(int argc, char** argv) {
    ::testing::InitGoogleMock(&argc, argv);
    return RUN_ALL_TESTS();
}

Mocha (Ruby)

Mocha permet d’utiliser les Mocks et les Stubs. Ne m’y connaissant pas spécialement en Ruby, je me suis peut-être trompé dans l’écriture des tests ; cependant, je trouve la syntaxe très épurée et surtout très lisible :

class MailServiceTest < Test::Unit::TestCase
    def test_member_mail_sent_when_subscribed
        member = Member.new("login", "password")
        msg = Message.new("Bienvenue login !")

        mailer = mock()
        mailer.expects(:send).with(:msg)

        MyApp.mailer = mailer
        MyApp.register(member)
    end
end

Flexmock (Ruby)

J’aime beaucoup FlexMock, qui s’intègre facilement avec les frameworks de test Ruby (Test::Unit et RSpec). La syntaxe est intéressante et l’API très complète. A première vue je le trouve plus exhaustif que Mocha :

class MailServiceTest < Test::Unit::TestCase
  def test_member_mail_sent_when_subscribed
    member = Member.new("login", "password")
    msg = Message.new("Bienvenue login !")

    mailer = flexmock("mailer")
    mailer.should_receive(:send).with(msg).times(1)

    MyApp.mailer = mailer
    MyApp.register(member)
  end
end

nMock (.Net)

Changeons encore d’univers pour assouvir notre soif d’apprendre. Cette fois-ci je vais écrire la classe de test complète afin de voir l’intérêt du « setUp ». Je trouve la syntaxe de nMock très propre, permettant de garder un code très structuré :

Réécrivons notre interface MailService :

public interface IMailService
{
    void Send(Message message);
}

Et notre classe de test :

[TestFixture]
public class MailServiceTest
{
    private Mockery mocks;
    private IMailService mailer;
    private Member member;
    private Message message;

    [SetUp]
    public void SetUp()
    {
        mocks = new Mockery();
        mailer = mocks.NewMock();
        member = new Member("login", "password");
        msg = new Message("Bienvenue login !");

        MyApp.setMailer(mailer);
    }

    [Test]
    public void MemberMailSentWhenSubscribed()
    {
        Expect.Once.On(mailer).Method("Send").With(msg);

        MyApp.register(member);
        mocks.VerifyAllExpectationsHaveBeenMet();
    }
}

Moq (.Net)

Moq, le concurrent de nMock, se veut plus minimaliste dans sa syntaxe. Je ne l’ai pas étudié en détail, le trouvant moins utile que nMock. J’ai cependant vu qu’il était utilisé par exemple dans les tests unitaires d’ASP.Net MVC.

var mailer = new Mock();
// ...
mock.Verify(mailer => mailer.Execute("Send"), Times.Once());
MyApp.register(member);

Il en existe sûrement des tas d’autres, pour divers autres langages (plus ou moins exotiques), mais l’intérêt de cet article était de vous montrer cette pratique des tests unitaires qui peine parfois à s’imposer face aux pratiques courantes des frameworks xUnit.

Et vous, que pensez-vous de cette technique ? En connaissez-vous d’autres similaires ou plus efficaces ?

Les textes, illustrations et démonstrations présents sur ce site sont la propriété de leurs auteurs respectifs, sauf mention contraire (photo de la bannière).
Chosesafaire.fr, un site propulsé par Wordpress, vous est proposé par Pierre Quillery & Killian Ebel.

Valid XHTML 1.0 Strict