Das Starten des Threads ist in eine Klasse OrderProcessor ausgelagert.
package com.mini.biz.order;
import com.mini.biz.tools.mail.EmailProcessor;
import com.mini.biz.fop.pdf.PdfGenerator;
import com.mini.biz.entities.cart.Cart;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
/**
* The {@link OrderProcessor} handles further processing once an
* order has been saved in the system.
*/
public class OrderProcessor
{
@Autowired
private TaskExecutor _taskExecutor;
@Autowired
private PdfGenerator _pdfGenerator;
@Autowired
private EmailProcessor _emailProcessor;
public void processOrder(final Cart cart)
{
Runnable runnable = new Runnable()
{
public void run()
{
m_pdfGenerator.generate(cart);
m_emailProcessor.sentEmailForNewOrder(cart);
}
};
_taskExecutor.execute(runnable);
}
}
Auf die Details vom PdfGenerator und EmailProcessor gehe ich an dieser Stelle nicht weiter ein. Wichtig ist der TaskExecutor. Er ist wie folgt konfiguriert.
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
<property name="queueCapacity" value="25" />
</bean>
Leider nimmt der TaskExecutor nur Runnable Objekte entgegen. Ich persönlich finde das mit Java5 eingeführte Callable Interface schöner – aber gut.
Mit dem Spring Framework und TestNG lässt sich das alles wunderbar testen. Das eingesetzte Web Framework Apache Wicket lassen wir mal ausser Acht.
@Test
@ContextConfiguration(locations={"/applicationContext.xml"})
public class ApproveAndOrderPanelTest extends AbstractTestNGSpringContextTests
{
Hier wird im Spring 2.5 Stil der ApplicationContext referenziert. TestNG Tests müssen leider (im Gegensatz zu JUnit 4.x Tests) immer noch von AbstractTestNGSpringContextTests ableiten. Übrigens sollte man AbstractTestNGSpringContextTests nicht vor Spring 2.5.2 einsetzen, da dort ein wichtiger Bug behoben wurde, der auftritt wenn man die komplette TestNG Suite laufen lässt (AbstractTestNGSpringContextTests executes Spring default callbacks in any case (marked with "alwaysRun = true"). Die @Test Annotation gehört zu TestNG. Kommen wir zum Code der vor den Tests ausgeführt wird:
@BeforeClass
public void before()
{
// Not mentioned here: delete existing PDF File
// Start Wiser SMTP
_wiser = new Wiser();
_wiser.setPort(2525);
_wiser.start();
// Alter MailSenderBean
JavaMailSenderImpl mailSender = (JavaMailSenderImpl) _mailSender;
mailSender.setPort(2525);
mailSender.setHost("localhost");
}
}
Hier starte ich einen Dummy SMTP Server von den Subversion Jungs. Anschliessend „verbiege“ ich die MailSender Bean damit der Dummy SMTP Server verwendet wird. Achtung, wenn Ihr weitere Unit Tests innerhalb der Suite laufen lasst, denkt dran dass Spring Beans in der Regel Singletons sind. Das „Verbiegen“ einer Bean schlägt also auch auf Folgetests durch, wenn es nicht in einer tearDown Methode wieder rückgängig gemacht wird. Nun der eigentliche (start gekürzte) Test.
public void testPanel() throws InterruptedException
{
// not shown: set up mock Bestellung aka Cart
assertEquals(0, _wiser.getMessages().size());
File pdfFile = _pdfGenerator.getPdfFile(cart);
assertFalse(pdfFile.exists());
// not shown: use WicketTester and FormTester to submit HTML Form and trigger order processing
ThreadPoolTaskExecutor threadPoolTaskExecutor = (ThreadPoolTaskExecutor) _taskExecutor;
int activeThreads = threadPoolTaskExecutor.getActiveCount();
while (activeThreads > 0)
{
TimeUnit.SECONDS.sleep(3);
activeThreads = threadPoolTaskExecutor.getActiveCount();
}
pdfFile = _pdfGenerator.getPdfFile(cart);
assertTrue(pdfFile.exists());
assertEquals(1, _wiser.getMessages().size());
// not shown: check that email contains attachment
}
Zunächst überprüfe ich, dass keine PDF Datei existiert und keine Email in der Queue vorhanden ist. Dann wir, hier nicht aufgeführter Code ausgeführt um den OrderProcessor anzuwerfen, so dass dieser einen neuen Thread spawned. Der Thread läuft im Spring konfigurierten TaskExecutor, welcher in Wirklichkeit ein ThreadPoolTaskExecutor ist. Da der TestNG Test weiterläuft, muss ich warten bis alle Threads (in meinem Fall also genau ein einziger Thread) im ThreadPoolTaskExecutor beendet sind. Anschliessend überprüfe ich, ob ein PDF erzeugt und eine Email versendet wurde.
Bleibt nur noch das Aufräumen.
@AfterClass
public void after()
{
_wiser.stop();
}
So richtig mächtig ist das Ganze eigentlich erst unter dem Gesichtspunkt, dass es sich dank WicketTester und FormTester eigentlich gar nicht mehr um einen Unit Test, sondern um einen GUI-Test (Integrationstest) handelt – auch wenn dieser Code hier nicht aufgeführt ist. Apache Wicket wird mit Testklassen ausgeliefert, die Selenium oder Watir (von Layout Aspekten abgesehen) überflüssig machen.
0 Kommentare:
Kommentar veröffentlichen