Come scrivere il caso di test di junit per il programma [chiuso]

2

Ho un metodo che ha dipendenze esterne all'interno del metodo. Per favore aiutami a scrivere un caso di test di junit per il programma qui sotto.

    @Override
    public void methodToTest(String user){

      //fetch the values from the properties file. Config class has a static method called
      //getProperty which reads the values from the environment specific property file.
      String url = Config.getProperty("serviceurl");
      String username = Config.getProperty("serviceusername");
      String password = Config.getProperty("servicepassword");

      //Connect to an external service.
      Service service = new Service(url, username, password);

      //invoke the method on the service.
      String returnValue = service.registerUser(user);

      if (returnValue.equals("failure")){
        throw new Exception("User could not be registered");
      }
    }

Nota:

  1. Il metodo sovrascrive il metodo della superclasse in modo che non possa cambiare il argomenti per contenere l'url, il nome utente e la password.
  2. Il metodo si collega anche a un servizio esterno. che è specifico per l'ambiente. Il i dettagli dell'ambiente sono presi dal file delle proprietà.
posta Pradeep 06.01.2013 - 05:02
fonte

3 risposte

3

Se sembra difficile testare un metodo, suggerisco di provare a testare i componenti del metodo.

Sembra che il tuo codice trarrebbe vantaggio da un po 'di refactoring.
Sembra che il tuo metodo abbia troppe responsabilità. Prova a suddividere il metodo in pezzi più piccoli.

Suggerisco di dare un'occhiata a quali azioni vengono attuate in ogni fase del metodo, invece di testare il metodo nel suo insieme. Suddividendo il metodo in blocchi più piccoli ti assicuri di ottenere una buona copertura di prova e puoi facilmente eseguire il drill-down per scoprire quali parti (se presenti) si rompono.

Sulla base del piccolo frammento che hai fornito e delle mie ipotesi su ciò che stai cercando di fare, le responsabilità includono:

  1. Recupero dei dettagli di accesso,
  2. Connessione a un servizio
  3. Aggiunta di un utente.

Queste tre diverse responsabilità dovrebbero essere testate in modo indipendente e dove uno fallisce, dovrebbe essere chiaro a quali passaggi (se qualcuno di questi) ha fallito.

public myClass extends myFoo {
    Service service;

    public myClass() {
      //fetch the values from the properties file. Config class has a static method called
      //getProperty which reads the values from the environment specific property file.
      String url = Config.getProperty("serviceurl");
      String username = Config.getProperty("serviceusername");
      String password = Config.getProperty("servicepassword");

      //Connect to an external service.
      service = new Service(url, username, password); //Error handling??
    }

    @Override
    public void registerUser(String user){
      //invoke the method on the service.
      String returnValue = service.registerUser(user);

      if (returnValue.equals("failure")){
        throw new Exception("User could not be registered");
      }
    }
}

Dai un'occhiata all'esempio sopra. Ci sono alcuni modi in cui puoi giocare con esso e senza saperne di più non sono sicuro quale sia la soluzione migliore per te, ma in sostanza il metodo che registra un utente non "cura" di come viene creato il servizio o di quale accesso i dettagli sono: tutto ciò di cui ha bisogno è un servizio per aggiungere l'utente specificato a.

Mi chiedo se il servizio debba essere un campo e / o contenuto nel costruttore - non c'è motivo per cui non debba essere spostato in un metodo a sé stante:

public myClass extends myFoo {
    @Override
    public void registerUser(String user){
      Service service = getService();

      //invoke the method on the service.
      String returnValue = service.registerUser(user);

      if (returnValue.equals("failure")){
        throw new Exception("User could not be registered");
      }
    }

    //Would be contained within the superclass (eg if Config shared between all subclasses), else overridden via the subclass (eg if each subclass had a different Config)
    protected Service getService() { //Error handling? eg throws InvalidCredentialsError? throws NetworkConnectionError? etc....
      //fetch the values from the properties file. Config class has a static method called
      //getProperty which reads the values from the environment specific property file.
      String url = Config.getProperty("serviceurl");
      String username = Config.getProperty("serviceusername");
      String password = Config.getProperty("servicepassword");

      //Connect to an external service.
      service = new Service(url, username, password);

      /**
      if(service == null) {
        throw new InvalidCredentialsError("The given url, username and password are invalid.");
      }
      **/

      return service;
    }
}

Sulla base delle mie ipotesi sulle responsabilità del codice, suddividerei il tuo metodo in tre (possibilmente quattro) serie di test come segue:

  1. La prima "azione" è il recupero: testare accuratamente Config.getProperty() .
    Se sei sicuro che questo metodo funzioni correttamente, puoi supporre che i valori di url / username / password verranno assegnati correttamente. Presumibilmente esiste anche un metodo Config.setProperty - per passare questi test, qualsiasi valore che passi in setProperty deve essere identico al valore che si recupera tramite Config.getProperty ().

  2. La prossima "azione" consiste nell'impostare Service() . Testare tutte le varianti di noto corretto / noto errato (e potenzialmente noto dannoso, vedere difetti di iniezione) / stringa vuota / valori null per url / username / password . Senza saperne di più sull'applicazione, non sono sicuro di quali siano i criteri di successo / fallimento (forse viene generata un'eccezione quando una percentuale non valida url / username /% co_de viene passata nel costruttore, ma ciò pone la domanda del perché non viene gettato / catturato nel codice attualmente visibile).

  3. L'ultima "azione" è tentare l'aggiunta di un utente.
    Osservando il metodo, suppongo che la funzionalità di base del metodo che stai tentando di testare sia l'aggiunta di un utente. Supponendo che sia passato il set # 1 e l'insieme dei test # 2, questo dovrebbe essere tanto semplice quanto passare valori che ci si aspetta di avere successo (es. "Jim"), valori che ci si aspetta di fallire o di cui non si è sicuri ( null, stringa vuota, stringhe estremamente lunghe ?? ecc.) e assicurandoti che il valore che ti aspetti di tornare effettivamente ritorni.
    Quello che noto è che dichiari che il servizio è un servizio esterno.
    Se ritieni che il servizio esterno abbia verificato accuratamente il codice, potresti non aver bisogno di questo passaggio (in tal caso, vai direttamente al punto 4).

  4. Un potenziale test finale consiste nel testare il metodo stesso.
    Questo probabilmente sarebbe identico all'insieme di test # 3, ma invece di testare il valore di ritorno del metodo registerUser (), si testerebbe per vedere se l'eccezione appropriata viene lanciata invece di testare il valore di ritorno ( s).

Il prossimo passo è scrivere le effettive classi di test JUnit;)

    
risposta data 06.01.2013 - 07:03
fonte
2

Oltre alla risposta @kwah excelent (+1) dovresti incapsulare il servizio esterno in un'interfaccia che può essere più facilmente simulata / simulata del vero servizio:

  //Connect to an external service.
  IService service = getService(url, username, password);

  //invoke the method on the service.
  String returnValue = service.registerUser(user);
    
risposta data 06.01.2013 - 10:51
fonte
2

Poiché le risorse sono esterne, nel tuo caso Config e Service , rende un po 'difficile da testare. Fondamentalmente hai due opzioni:

  1. Vai con il flusso e imposta le risorse esterne sotto il tuo controllo, se possibile
  2. Rifatta il codice in modo che sia più facile prendere in giro le risorse

Dato che il primo che chiaramente non puoi fare, passeremo attraverso il secondo. Refactoring del codice con ogni passo che risolve ogni dipendenza in modo che siano sostituibili per il test:

Correzione della dipendenza da Config

Suppongo che Config sia una risorsa statica, il che significa che devi essere in grado di sostituirlo in qualche modo. La cosa più semplice è aggiungere una nuova risorsa sostituibile:

public interface IConfigResource {
    String getProperty(String propertyName);
}

Insieme a una versione statica predefinita di dandy:

public class DefaultConfigResource : IConfigResource {
    public String getProperty(String propertyName) {
        return Config.getProperty(propertyName);
    }
}

Nella tua classe devi aggiungere / modificare quanto segue in modo che la dipendenza possa essere iniettata per il test:

// The default is set at construction
private IConfigResource configResource = new DefaultConfigResource();

// Method used by the test to inject the config resource
public void setConfigResource(IConfigResource configResource) {
    this.configResource = configResource;
}

Ora possiamo utilizzare Mockito o manualmente manualmente una versione falsa di IConfigResource per verificare che il tuo metodo sia chiamando correttamente la risorsa config, nel test in basso usiamo Mockito:

ClassUnderTest cut;
IConfigResource mockedResource;

@Before
public void setup() {
    cut = ... ; // setup for class under test

    // We mock with mockito and inject the resource
    mockedResource = mock(IConfigResource.class);
    cut.setConfigResource(mockedConfigResource);
}

@Test
public void callingMethod_ShouldGetValuesFromResource() {
    // Arrange
    // might change the return values to reflect the correct ones
    when(mockedResource.getProperty("serviceurl"))
        .thenReturn("correct service url");
    when(mockedResource.getProperty("serviceusername"))
        .thenReturn("correct service username");
    when(mockedResource.getProperty("servicepassword"))
        .thenReturn("correct service password");

    // Act
    cut.methodToTest("username");

    // Assert, with mockito
    verify(mockedResource).getProperty("serviceurl");
    verify(mockedResource).getProperty("serviceusername");
    verify(mockedResource).getProperty("servicepassword");
}

Lì, la parte% del parametro% co_de del metodo dovrebbe ora essere testata

Correzione della dipendenza da Config (e rimozione della dipendenza Config come bonus)

Quindi abbiamo bisogno di qualcosa che deve gestire questa parte del codice:

//Connect to an external service.
Service service = new Service(url, username, password);

//invoke the method on the service.
String returnValue = service.registerUser(user);

Per prima cosa il servizio deve avere un'interfaccia sostituibile per consentirci di verificare che il tuo metodo stia chiamando Service . Questo dovrebbe essere facile:

public interface IService {
   String registerUser(String userName);
}

Il tuo attuale servizio può implementare questa interfaccia con facilità, basta aggiungerla alla firma della classe (poiché dovrebbe avere già il metodo):

public class Service implements IService {
...

Abbiamo anche bisogno di qualcosa (un metodo di fabbrica) che possa connettersi al servizio e restituire qualcosa che assomiglia a service.registerUser(String) . Possiamo anche aggiungere la risorsa di configurazione che abbiamo creato sopra per renderla un po 'più semplice:

public interface IServiceConnector {
    void setConfigResource(IConfigResource configResource);

    // no need to have parameters, the config is injected above
    IService getService();
}

L'implementazione di default è semplice, possiamo usare IService che abbiamo usato prima:

public class DefaultServiceConnector {
    IConfigResource configResource = new DefaultConfigResource();

    public void setConfigResource(IConfigResource configResource) {
        this.configResource = configResource;
    }

    public IService getService() {
        String url = Config.getProperty("serviceurl");
        String username = Config.getProperty("serviceusername");
        String password = Config.getProperty("servicepassword");

        return new Service(url, username, password);
    }
}

Ora puoi spostare il test scritto sopra per essere utilizzato come test per questa classe di connettori in cui il metodo in prova è ConfigResource . Inoltre il tuo metodo originale sotto test dovrebbe essere più semplice ora:

IServiceConnector serviceConnector = new DefaultServiceConnector();

public void setServiceConnector(IServiceConnector serviceConnector) {
    this.serviceConnector = serviceConnector;
}

@Override
public void methodToTest(String user) {
  // ... Removed some code ...

  //Connect to an external service.
  Service service = serviceConnector.getService();

  //invoke the method on the service.
  String returnValue = service.registerUser(user);

    if (returnValue.equals("failure")){
      throw new Exception("User could not be registered");
    }
}

Il test dovrebbe essere simile a questo ora:

ClassUnderTest cut;
IServiceConnector mockedConnector;
IService mockedService;

@Before
public void setup() {
    cut = ... ; // setup for class under test

    // We mock with mockito and inject the resource
    mockedConnector = mock(IServiceConnector.class);
    cut.setServiceConnector(mockedConnector);

    // We mock the service to be used
    mockedService = mock(IService.class);
}

@Test
public void registerUser_ShouldRegisterToService() {
    // Arrange
    // might change the return values to reflect the correct ones
    when(mockedConnector.getService())
        .thenReturn(mockedService);
    String newUsername = "newusername";

    // Act
    cut.methodToTest(newUsername);

    // Assert, with mockito
    // we only need to assert that the service registers the 
    // new user
    verify(mockedService).registerUser(username);
}

Puoi anche testare il lancio delle eccezioni:

// test the exception
@Test(expected = Exception.class)
public void registerUser_ThrowExceptionOnError() {
    // Arrange
    when(mockedConnector.getService())
        .thenReturn(mockedService);
    // return failure on any string
    when(mockedService.registerUser(anyString))
        .thenReturn("failure");
    String newUsername = "newusername";

    // Act
    cut.methodToTest(newUsername);

    // Assert
    // Should throw exception and be checked with the
    // expected parameter in test
}

Spero che questo ti aiuti.

    
risposta data 06.01.2013 - 10:20
fonte

Leggi altre domande sui tag