2010-02-13 18 views
13

Quelqu'un peut-il faire des suggestions sur la meilleure façon d'utiliser EasyMock pour attendre un appel à Runtime.getRuntime().exec(xxx)?Mock Runtime.getRuntime()?

Je pourrais déplacer l'appel dans une méthode dans une autre classe qui implémente une interface, mais ne préfèrerait pas dans un monde idéal.

interface RuntimeWrapper { 
    ProcessWrapper execute(String command) throws IOException; 
} 

interface ProcessWrapper { 
    int waitFor() throws InterruptedException; 
} 

Je me demandais si quelqu'un avait d'autres suggestions?

Répondre

13

Votre classe ne doit pas appeler Runtime.getRuntime(). il doit s'attendre à ce que Runtime soit défini comme dépendante et fonctionne avec. Ensuite, dans votre test, vous pouvez facilement fournir un simulacre et le définir comme une dépendance.

En tant que sidenote, je suggère de regarder this lecture on OO Design for testability.

Mise à jour: Je n'ai pas vu le constructeur privé. Vous pouvez essayer d'utiliser java bytecode instrumentation afin d'ajouter un autre constructeur ou rendre public le constructeur, mais cela pourrait aussi s'avérer impossible (s'il y a des restrictions sur cette classe).

Donc, votre option est de faire un wrapper (comme vous l'avez suggéré dans la question), et de suivre l'approche de l'injection de dépendances.

+1

Merci pour la suggestion - Je suis d'accord que l'injection de la dépendance est la meilleure façon, mais je préférerais moquer. Cependant, je ne vois pas comment obtenir une instance simulée de Runtime - ce n'est pas une interface et je ne suis pas sûr de pouvoir la sous-classer car elle a un constructeur privé. Peut-être qu'il me manque quelque chose? – Rich

+0

yup, cela le rend presque impossible. Vérifiez ma mise à jour. – Bozho

+0

Je vais aller avec l'approche wrapper :) Merci encore! – Rich

0

Peut-être au lieu de se moquer de Runtime.getRuntime().exec() vous pouvez "simuler" le script/programme/etc. c'est censé appeler. Au lieu de passer la vraie ligne de commande dans exec(), écrivez un script de test et exécutez-le à la place. Vous pourriez faire en sorte que le script renvoie des valeurs codées en dur que vous pourriez tester comme une classe raillée.

+0

C'est ce que j'ai essayé au début, mais j'ai trouvé quelques problèmes avec ça. Tout d'abord, il rompt l'indépendance de la plate-forme des tests (même si le code est conçu pour Windows, les tests sont souvent exécutés sur une machine Linux) et deuxièmement, pour une raison quelconque, je me moque du script. Probablement parce que j'ai peur de le vérifier :) Aussi, l'exécution moqueuse me permet de simuler différents scénarios plus facilement. Merci quand même! – Rich

6

Bozho ci-dessus est IMO le Corriger la solution. Mais ce n'est pas la seule solution. Vous pouvez utiliser PowerMock ou JMockIt.

En utilisant PowerMock:

package playtest; 

public class UsesRuntime { 
    public void run() throws Exception { 
     Runtime rt = Runtime.getRuntime(); 
     rt.exec("notepad"); 
    } 
} 


package playtest; 

import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.powermock.core.classloader.annotations.PrepareForTest; 
import org.powermock.modules.junit4.legacy.PowerMockRunner; 

import static org.powermock.api.easymock.PowerMock.*; 
import static org.easymock.EasyMock.expect; 

@RunWith(PowerMockRunner.class) 
@PrepareForTest({ UsesRuntime.class }) 
public class TestUsesRuntime { 

    @Test 
    public void test() throws Exception { 
     mockStatic(Runtime.class); 
     Runtime mockedRuntime = createMock(Runtime.class); 

     expect(Runtime.getRuntime()).andReturn(mockedRuntime); 

     expect(mockedRuntime.exec("notepad")).andReturn(null); 

     replay(Runtime.class, mockedRuntime); 

     UsesRuntime sut = new UsesRuntime(); 
     sut.run(); 
    } 
} 
+0

Merci pour la suggestion, je n'avais pas entendu parler de Powermock avant. – Rich

0

Voici comment vous le feriez avec EasyMock 3.0 (et JUnit 4):

import org.junit.*; 
import org.easymock.*; 
import static org.easymock.EasyMock.*; 

public final class EasyMockTest extends EasyMockSupport 
{ 
    @Test 
    public void mockRuntimeExec() throws Exception 
    { 
     Runtime r = createNiceMock(Runtime.class); 

     expect(r.exec("command")).andReturn(null); 
     replayAll(); 

     // In tested code: 
     r.exec("command"); 

     verifyAll(); 
    } 
} 

Le seul problème avec le test ci-dessus est que les besoins de l'objet Runtime être passé au code testé, ce qui l'empêche d'utiliser Runtime.getRuntime(). Avec JMockit, d'autre part, le test peut écrire, en évitant ce problème:

import org.junit.*; 
import mockit.*; 

public final class JMockitTest 
{ 
    @Test 
    public void mockRuntimeExec() throws Exception 
    { 
     final Runtime r = Runtime.getRuntime(); 

     new NonStrictExpectations(r) {{ r.exec("command"); times = 1; }}; 

     // In tested code: 
     Runtime.getRuntime().exec("command"); 
    } 
}