Static methods vs. instance method in testing java

4

I have a class SystemPropertyHelper in the web service layer, which has only static methods to recover objects from the core of the application, this in order not to mix functionalities between layers, and the handling of exceptions among others. things.

The issue is that I'm testing the service layer and I need to mock this helper. My question is, what implications would make the methods of this helper class not static, that is, to use them I would have to create instances of it.

The reason that leads me to do it, is because investigating recommends to the extent that the application allows it, refactor and convert these static methods into instance methods, so as not to resort to libraries like PowerMock. I can do it perfectly, since this service phase is starting, but I have this doubt, on the one hand they say that a method should be static if it does not save or modify the state, which is in fact my case, the only thing that makes the SystemPropertyHelper is to bring me the object and deliver it to the service layer, and on the other hand recommend making the non-static methods for a better applicability in the testing, I hope you understand the dilemma. Thanks

    
asked by José Oliveros 14.11.2016 в 21:25
source

1 answer

1

The impact of declaring a utility class depends first of all on the question whether there are states involved. If all the methods of the utilitarian class do not need context, there is no problem in replacing them with an installed service. I give a simplified example:

Case 1 - class without status

public class Conv{

    private static final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

    public static String formatDato(Date d){
        return sdf.format(d);
    }

    public static Date dateDeString(String dato) throws ParseException {
        return sdf.parse(dato); 
    }
}

// uso
String fecha = Conv.formatDato(new Date());

This class has no status, so its use can be re-invoiced to:

public class Conv implements IConv{

    public static final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

    public Conv(){};

    public String formatDato(Date d){
        return sdf.format(d);
    }

    public Date dateDeString(String dato) throws ParseException {
        return sdf.parse(dato); 
    }
}

// uso
String fecha = new Conv().formatDato(new Date());

Assuming that we define the interface IConv with the methods formatDato and dateDeString we can create a mock and exchange the classes.

Case 2 - class with status:

Let's imagine the same kind of utility, but with one more method that makes it have a status:

public class Conv{

    private static final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

    public static String formatDato(Date d){
        return sdf.format(d);
    }

    public static Date dateDeString(String dato) throws ParseException {
        return sdf.parse(dato); 
    }

    // aquí existe la posibilidad que el formato y así el estado cambia
    public static void nuevoFormato(String formato)
              throws NullpointerException, IllegalArgumentException {
        sdf.applyPattern(format);
    }
}

// uso
String fecha = Conv.formatDato(new Date());

Because the class has a state, you are not allowed to apply the same pattern as in the other example. In an ideal world you realize that it is very unwise to manage states in classes that provide static methods.

The recommended solution

You already noticed how two "best practices" in the case of "test driven development" cause problems, then remember that there are no best practices because yes, there are only bad and good strategies for a context.

In your case one way to get rid of all the headaches is to follow some simple rules in the design:

  • design all the services as an interface.
  • we implement the services according to context (test, debug, release)
  • we create a Context that has methods to obtain the services.
  • instead of using static utilities we deliver utility classes such as singleton

The Context for example could look like this:

public class Context {

    public static final int TEST = 0;
    public static final int DEBUG = 1;
    public static final int RELEASE = 2;
    private static Context testContext = null;
    private static Context debugContext = null;
    private static Context releaseContext = null;
    private IDataBaseService dataBaseService=null;
    private IFileService fileService=null;
    private ITcpService tcpService=null;

    private int build=0;

    // método fabrica para obtener el contexto para el build
    // un singleton a demanda.
    public static Context getContext(Integer build){
        switch(build){

        case TEST:
            return (null==testContext) ? testContext = new Context(TEST) : testContext;
        case DEBUG:
            return (null==debugContext) ? debugContext = new Context(DEBUG) : debugContext;
        case RELEASE:   
        default:    
            return (null==releaseContext) ? releaseContext = new Context(RELEASE) : releaseContext;

        }
    }

    // los métodos de instancia de contexto también pueden entregar singleton según necesidad
    private Context(int build){ this.build = build; } 

    public IFileService getFileService(){
        if (null==fileService){
            switch(build){

            case TEST:
                FileService = new MockFileService();
            case DEBUG:
                fileService = new DebugFileService();
            case RELEASE:   
            default:    
                fileService = new FileService();

            }
        }
        return dataBaseService;
    }

    // ....
}

With this design we can create and obtain the corresponding context in any build variant with for example Context context=Context.getContext(Context.TEST); and then the services simply with IFileService fs = context.getFileService() for the appropriate context.

    
answered by 05.02.2017 в 01:28