Como testar um monitor de arquivos?

Bom dia, caros!

Desenvolví um monitor de arquivos, para que o mesmo fique sempre observando um .properties e, se esse .properties for alterado, ele recarrega as propriedades do sistema. Até aqui, tudo bem, mas… como eu desenvolvo um teste unitário para esse código? Qualquer dica é bem vinda!

Segue abaixo o código:


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class PropertiesWatchdog extends TimerTask{
	
	
	private final Log logger = LogFactory.getLog(PropertiesWatchdog.class);
	
	public static final String SOURCE_FILE = "C:\\SOA\\Codigo\\Fachada\\FachadaSoaACrm\\ConsultarClienteFSACrm\\trunk\\config\\FachadaSoaACrm-AmdocsServices.properties";
	
	private File sourceFile;
	
	private Properties properties;
	
	private long timeToUpdate;
	
	private long lastModified;
	
	private boolean started;
	
	protected PropertiesWatchdog(File sourceFile) {
		this(sourceFile, 5000);
	}
	
	protected PropertiesWatchdog(File sourceFile, long timeToUpdate) {
		this.sourceFile = sourceFile;
		this.timeToUpdate = timeToUpdate;
		start();
	}
	
	
	private static PropertiesWatchdog instance = new PropertiesWatchdog(new File(SOURCE_FILE));
	
	public static PropertiesWatchdog getInstance() 
	{
		return instance;
	}
	
	
	protected boolean isStarted() {
		return started;
	}
	
	protected void setStarted(boolean started) {
		this.started = started;
	}
	
	protected long getLastModified() {
		return lastModified;
	}
	
	protected void setLastModified (long lastModified) {
		this.lastModified = lastModified;
	}
	
	
	public void start() {
		if (isStarted()) {
			throw new RuntimeException("This watchdog cannot be restarted.");
		}
		Timer timer = new Timer(true);
		timer.schedule(this, 0, timeToUpdate);
		setStarted(true);
	}
	
	
	public synchronized Properties getProperties() {
		while (this.properties == null) {
			try {
				wait();
			} catch (InterruptedException e) {
				return null;
			}
		}
		notifyAll();
		return new Properties(this.properties);
	}
	
	@Override
	public void run() {
		long lastModified = sourceFile.lastModified();
		if (lastModified > getLastModified()) {
			synchronized (this) {
				Properties props;
				FileInputStream fis = null;
				try {
					props = new Properties();
					props.load(fis = new FileInputStream(sourceFile));
					this.properties = props;
					setLastModified(lastModified);
				}
				catch (IOException ex) {
					logger.warn("IOException thrown  when trying to read the file " + SOURCE_FILE, ex);				
				}
				finally {
					if (fis != null) {
						try {
							fis.close();
						} catch (IOException e) {
							logger.warn("IOException thrown  when trying to close the stream to the file " + SOURCE_FILE, e);
						}
					}
				}
				notifyAll();
			}
		}
	}
	
}

Grato!

O load do properties não aceita apenas uma FileInputStream, mas qualquer Stream.

Se fosse testar essa classe, eu montaria um Stream de mock do arquivo. E alteraria ela para que eu pudesse dizer qual stream usar (uma possibilidade seria fazer isso por injeção de dependência). O padrão seria seu Stream atual, no teste unitário seria o Stream de testes.

Com um stream de testes vc poderia fazer alterações em memória e ver se o WatchDog as detectou.

Viny, obrigado pela dica! Ela levou ao refactoring da classe, que eu deixo aqui para quem interessar:



import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class PropertiesWatchdog extends TimerTask{
	
	
	private final Log logger = LogFactory.getLog(PropertiesWatchdog.class);
	
	private DataProvider provider;
	
	private Properties properties;
	
	private long timeToUpdate;
	
	private long lastModified;
	
	private boolean started;
	
	protected PropertiesWatchdog(DataProvider provider) {
		this(provider, 5000);
	}
	
	protected PropertiesWatchdog(DataProvider provider, long timeToUpdate) {
		this.provider = provider;
		this.timeToUpdate = timeToUpdate;
		start();
	}
	
	
	public static PropertiesWatchdog getInstance(DataProvider provider) {
		return new PropertiesWatchdog(provider);
	}
	
	protected boolean isStarted() {
		return started;
	}
	
	protected void setStarted(boolean started) {
		this.started = started;
	}
	
	protected long getLastModified() {
		return lastModified;
	}
	
	protected void setLastModified (long lastModified) {
		this.lastModified = lastModified;
	}
	
	protected DataProvider getProvider() {
		return this.provider;
	}
	
	
	public void start() {
		if (isStarted()) {
			throw new RuntimeException("This watchdog cannot be restarted.");
		}
		Timer timer = new Timer(true);
		timer.schedule(this, 0, timeToUpdate);
		setStarted(true);
	}
	
	
	public synchronized Properties getProperties() {
		while (this.properties == null) {
			try {
				wait();
			} catch (InterruptedException e) {
				return null;
			}
		}
		notifyAll();		
		return new Properties(this.properties);
	}
	
	@Override
	public void run() {
		logger.debug("Running the watchdog timer task..");
		DataProvider provider = getProvider();
		long lastModified = provider.getLastUpdate();
		if (lastModified > getLastModified()) {
			logger.info("Re-reading the data source " + provider.toString());
			synchronized (this) {
				Properties props;
				InputStream fis = null;
				try {
					props = new Properties();					
					props.load(fis = provider.openStream());
					this.properties = props;
					setLastModified(lastModified);
				}
				catch (IOException ex) {
					logger.warn("IOException thrown  when trying to read the data source " + provider.toString(), ex);				
				}
				finally {
					if (fis != null) {
						try {
							fis.close();
						} catch (IOException e) {
							logger.warn("IOException thrown  when trying to close the stream to the file " + provider.toString(), e);
						}
					}
				}
				notifyAll();
			}
		}
	}
	
	public static void main(String[] args) {
		DataProvider provider = new FileDataProvider(new File("C:\\SOA\\Codigo\\Fachada\\FachadaSoaACrm\\ConsultarClienteFSACrm\\trunk\\config\\FachadaSoaACrm-AmdocsServices.properties"));
		PropertiesWatchdog propertiesWatchdog = PropertiesWatchdog.getInstance(provider);
		System.out.println(propertiesWatchdog.getProperties());
	}
}

import java.io.IOException;
import java.io.InputStream;

public abstract class DataProvider {
	
	
	public abstract long getLastUpdate();
	
	public abstract InputStream openStream() throws IOException;

}

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileDataProvider extends DataProvider {

	private File file;
	
	
	public FileDataProvider(File file) {
		if (file == null || ! file.exists())
			throw new IllegalArgumentException("File cannot be null");
		this.file = file;
	}
	
	
	protected File getFile() {
		return this.file;
	}
	
	@Override
	public long getLastUpdate() {
		return getFile().lastModified();
	}

	@Override
	public InputStream openStream() throws IOException {
		return new FileInputStream(getFile());
	}
	
	
	@Override
	public String toString() {
		
		return new StringBuilder("{File: ").append(getFile()).append("}").toString();
	}

}

Vale notar que, ao contrário de antes, a classe deixou de ser singleton. Mantive o método getInstance para poder manter ao máximo a compatibilidade com o modelo de singletons, mas não conseguiria testar se continuasse sendo um.

[]'s