# junit best practices



## mouk (10. Okt 2009)

Hi!

Ich beschäftige mich derzeit mit junit. Habe auch schon einige Dokumentation gelesen, darin wird aber nur die grundlegende Benutzung von Junit behandelt.
Deshalb habe ich einige Fragen bezüglich wie man seine Klassen am besten Testen soll:

Angenommen ich habe eine Klasse mit internen Datenstrukturen, die durch Daten in externen Textdateien gefüllt werden. Wie kann ich nun testen, ob die Daten aus den Textdateien korrekt eingelesen und interpretiert werden? Oder testet man so etwas nicht mit junit, sondern nur die Interfaces der Klassen?
Oder soll ich eine Methode machen, von der die Datenstrukturen zurückgeliefert werden, sodass eine Junit-Test-Methode diese verifizieren kann? (aber ist das nicht schlechter Stil?)

Weiters ist mir aufgefallen dass meine Testklassen schnell relativ groß werden, fast so groß wie die Klasse, die ich damit teste. Ist das normal oder mach ich da was falsch?

Habt ihr sonst vielleicht noch nützliche Tipps oder Best Practices im Umgang mit Junit?

Ich hoffe jemand mit mehr Erfahrung als ich kann mir weiterhelfen.

lg, mouk


----------



## maki (10. Okt 2009)

Es gibt viel best Practices für Junit, in Blog-/Artikel- und Buchform (zB. XUnit).
Der Begriff Mock sagt dir etwas?

Wenn du deinen Code zeigst, kann man vielleicht konkretere Tipps geben.


----------



## mouk (10. Okt 2009)

Danke für die Antwort.

Ich denke ich werde mir mal Junit in Action bestellen um ein besseres Verständnis zu erlangen.
Mit Mock meinst du wahrscheinlich Mock-Objekte?


----------



## bygones (11. Okt 2009)

jo (siehe mockito, easymock etc)


----------



## hibulaire (10. Dez 2009)

Hallo Zusammen, ich habe ein kleines Problem mit meiner Anwendung. Ich möchte Test für eine Batchprocessor ausführen mit Easymock und Junit. Ich möchte gern die Methoden addBatchExecutionJob() und getBatchResult() testen. Ich benutze Eclipse 3.5 und der Betriebssystem ist Win XP. Hier ist meine Code:
	
	
	
	





```
package de.kdg.sbp2.preprocessor.batch;

import static org.junit.Assert.*;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

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


public class BatchExecutionJobTest {
	
	private int batch1Value= 10;
	private int batch2Value= 16;
	private int batch3Value= 30;
	private int batch4Value= 55;
	private int batch5Value= 29;
	private String batch1Names= "batch_snmp1";
	private String batch2Names= "batch_snmp2";
	private String batch3Names= "batch_snmp3";
	private String batch4Names= "batch_snmp4";
	private String batch5Names= "batch_snmp5";
	private BatchHandler batchHandler;
	private BatchExecutionJob batchExecutionJob;
	private Map<Integer, String> batchLinesMap;
	private BatchExecutionJob job;
	private int batchId;
	
	@Before
	public void before(){
		batchHandler= EasyMock.createMock(BatchHandler.class);
		batchExecutionJob= new BatchExecutionJob();
		
		batchLinesMap= new HashMap<Integer, String>(){{
			put(batch1Value, batch1Names);
			put(batch2Value, batch2Names);
			put(batch3Value, batch3Names);
			put(batch4Value, batch4Names);
			put(batch5Value, batch5Names);
		}};
	}
	
	@After
	public void after(){
		EasyMock.verify(batchHandler);
	}
	
	@Test
	@SuppressWarnings("Serial")
	public void testAddBatchExecutionJob() throws BatchException{
		
		EasyMock.expect(batchHandler.getBatch(batchId)).andReturn(batchLinesMap);
		EasyMock.replay(batchHandler);
		
		assertNotNull("batchExecutionJob is null", batchExecutionJob);
		assertNotNull("batchHandler is null", batchHandler);
		Map<Integer, String> actual= batchExecutionJob.getBatchMap();
		
		assertNotNull("actual is null", actual);
		
		Map<Integer, String> expected= new HashMap<Integer, String>(){{
			put(batch1Value, batch1Names);
			put(batch2Value, batch2Names);
		}};
		
		assertEquals(expected, actual);
	}
}
```
und ich bekomme als error bei Testen : java.lang.AssertionError: actual is null.
Unter sind die benötigte Klasse:

```
public interface BatchHandler {

	/**
	 * This adds a batch-execution-job to the job-scheduler
	 * of a batch-processor. it returns the id that was given
	 * to the batch by the job-scheduler.
	 * 
	 * @param job			The job to be executed by
	 * 						the batch-processor, with
	 * 						a certain batch and within certain timeFrames.
	 * @return				the id that was given to the
	 * 						batch by the job-scheduler.
	 */
	int addBatchExecutionJob(BatchExecutionJob job);
	
	/**
	 * returns a Map with line numbers as keys and
	 * lines of the batch as values.
	 * 
	 * @param batchId		The id of the batch to be returned.
	 * @return				A Map with line-numbers as 
	 * 						keys and lines of the batch as 
	 * 						values.
	 */
	Map<Integer, String> getBatch(int batchId);
	
	/**
	 * This returns <code>true</code>, if and only if the batch-execution is finished.
	 * 
	 * @param batchId		The id of the batch which is or is not fully executed.
	 * @return				<code>true</code>, if and only if the batch-execution is finished.
	 */
	boolean isJobFinished(int batchId);
	
	/**
	 * This gets the results of the execution of the lines of a
	 * batch. The batch is identified by the <code>batchId</code>.
	 * 
	 * @param batchId		Id of the batch which produced the desired results.
	 * @return				A Map of line numbers as keys and results of the
	 * 						execution of that lines as values.
	 */
	Map<Integer, String> getBatchResults(int batchId);
}
```
 und

```
package de.kdg.sbp2.preprocessor.batch;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import de.kdg.sbp2.job.TimeFrame;

/**
 * Objects of this can be used to handle a 
 * batch execution job to the job-scheduler of a batch-processor
 * via a <code>BatchHandler</code>. At least you must set a 
 * batch to this job, either by a file, or by a <code>Reader</code>
 * or by an <code>InputStream</code>. TimeFrames are optional.
 * If there is none, the batch may be executed any time.
 * 
 * 
 * 
 * @see BatchHandler
 */
public class BatchExecutionJob {
	
	public final static int BATCH_MISSING = -1;
	
	private String lineSeparator = System.getProperty("line.separator");
	private Map<Integer, String> batchLinesMap = null;
	private Map<Date, TimeFrame> timeFrameMap;
	private Map<Date, Long> timeSpanMap;
	
	/**
	 * A simple constructor. Don't forget to set a batch, after you
	 * used this.
	 */
	BatchExecutionJob() {
		timeFrameMap = new HashMap<Date, TimeFrame>();
	}
	
	/**
	 * This gets the Map with all the batch lines, and with the 
	 * line numbers as keys.
	 * 
	 * @return				Map which contains the Batch.
	 */
	public Map<Integer, String> getBatchMap() {
		return batchLinesMap;
	}
	
	public Map<Date, TimeFrame> getTimeFrameMap() {
		return timeFrameMap;
	}
	
	public Map<Date, Long> getTimeSpanMap() {
		return timeSpanMap;
	}

	/**
	 * This sets a batch from a file.
	 * 
	 * @param batchFile		The file that contains the batch.
	 */
	public void setBatchFromFile(File batchFile) {
		if (batchFile != null && batchFile.canRead()) {
			Reader reader;
			try {
				reader = new FileReader(batchFile);
				setBatchFromReader(reader);
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * This sets a batch from an <code>InputStream</code>.
	 * 
	 * @param stream		The <code>InputStream</code> that
	 * 						contains the batch.
	 */
	public void setBatchFromInputStream(InputStream stream) {
		if (stream != null) {
			Reader reader = new InputStreamReader(stream);
			setBatchFromReader(reader);
		}
	}
	
	/**
	 * This sets a batch from a <code>Reader</code>.
	 * 
	 * @param reader		The <code>Reader</code> that
	 * 						contains the batch.
	 */
	public void setBatchFromReader(Reader reader) {
		if (reader != null) {
			int charsRead = 0;
			String[] lines = null;
			String fileContent = "";
			char[] buffer = new char[80];
			try {
				charsRead = reader.read(buffer);
				while (charsRead != -1) {
					fileContent += String.valueOf(buffer, 0, charsRead);
					charsRead = reader.read(buffer);
				}
				lines = fileContent.split(lineSeparator);
				batchLinesMap = new HashMap<Integer, String>();
				for (int i = 0; i < lines.length; i++) {
					batchLinesMap.put(i+1, lines[i]);
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * This adds a timeFrame to the batch, specified by 
	 * begin and end-date.
	 * 
	 * @param beginDate			The time where execution
	 * 							is permitted to start.
	 * @param endDate			The time where execution
	 * 							must stop again.
	 */
	public void addTimeFrame(Date beginDate, Date endDate) {
		TimeFrame timeFrame = new TimeFrame(beginDate.getTime(), endDate.getTime());
		timeFrameMap.put(beginDate, timeFrame);
	}
	
	/**
	 * 	 * This adds a periodic timeFrame to the batch, specified by 
	 * begin and end-date. The <code>timeSpan</code> is a number
	 * of milliseconds between the first and any subsequent starting
	 * times.
	 * 
	 * @param beginDate			The time where execution
	 * 							is permitted to start.
	 * @param endDate			The time where execution
	 * 							must stop again.
	 * @param timeSpan			The number of millisecond between
	 * 							the first and any subsequent starting
	 * 							times.
	 */
	public void addPeriodicTimeFrameMap(Date beginDate, Date endDate, Long timeSpan) {
		addTimeFrame(beginDate, endDate);
		timeSpanMap.put(beginDate, timeSpan);
	}
	
	/**
	 * This checks if the job has all parameters set.
	 * This is fist of all: if a batch is set.
	 * 
	 * @return					True, if a batch is set.
	 */
	public boolean isValid() {
		return batchLinesMap != null;
	}
}
```
Ich bin für jede Hilfe dankbar.


----------



## mvitz (10. Dez 2009)

Ich verstehe irgendwie nicht genau, wo dein Problem ist...

du initialisierst im BatchExecutionJob die Map mit dem Wert null. Im Test rufst du getBatchMap auf, welche dann logischerweise diese Map (null) zurück gibt und dein Test schlägt deshalb fehl.

Ich verstehe auch nicht, wieso du vorher die ganzen Mocksachen aufbaust, wenn das Mock Objekt nicht an die zu testende Klasse übergeben wird, der Mock wird nirgendwo benutzt, also brauchst du den doch garnicht...


----------



## hibulaire (10. Dez 2009)

danke mvitz, dass du so schnell auf mein Post geantwortet hast. Allerdings laut mein problem so: ich möchte jedes Batch an einem Batch Controller schicken und testen ob der BatchController dieser Batch bekommen hast. Ich dachte, man könnte es mit Mock Objekte machen.


----------

