Archive for the ‘Testy’ Category

Testing with Spring 2.5

Wednesday, December 12th, 2007

Besides other cool features, Spring 2.5 brought completely rewritten support for functional tests. Before this version, Spring had support for functional tests using JUnit 3 only. If you needed to use JUnit 4 or TestNG you just did not get any help from the framework

Now you can use brand new annotations and you are not constrained in your choice of testing framework any more. For complete information, please consult Spring reference, I will show only small subset of the new features. Let’s take a look on the first example.



01 @RunWith(SpringJUnit4ClassRunner.class)
02 @ContextConfiguration(locations="classpath:applicationContext.xml")
03 @Transactional
04 public class TestJpaClientDao {
05 
06   private static final long PERSONAL_NUM = 123L;
07   @Autowired
08   private ClientDao clientDao;
09   
10   @Test
11   public void testCreateClient()
12   {
13     Client client = new Client(PERSONAL_NUM);
14     client.setName("John Doe");
15     client.addAddress(new Address("Old st.","2a","Prague","120 00"));
16     client.addAccount(new Account("123-4560789"));
17     client.addAccount(new Account("888-8888888"));
18     clientDao.createClient(client);
19     
20     Client loadedClient = clientDao.loadClientWithPersonalNumber(PERSONAL_NUM);
21     assertSame(client, loadedClient);
22   }

Here we see normal class with some annotations. But it is not normal class, it is JUnit 4 test. By RunWith annotation we are extending usual JUnit test runner in order to enable Spring support. Then we have to define where application context XML file(s) are (@ContextConfiguration annotation). Now we can use Autowire annotations to have all necessary dependencies injected. If we use annotation Transactional we obtain similar behavior as when we were using old AbstractTransactionalSpringContextTests. It means:

  1. The application context is shared for all test methods (in all test classes). So all the stuff is initialized only once. If your test does some changes to the application context and it has to be therefore discarded, you can use DirtiesContext annotation.
  2. All test methods are run in transaction which is rolled back at the end of every test method. You can change this behavior by Rollback annotation or by TransactionConfiguration annotation.

The new Spring test support is great. I just miss one thing. Old test support classes gave me possibility to finish and start a transaction in the middle of the test method (by calling setComplete, finishTransaction and startNewTransaction). I agree, that you do not need such feature very often, but sometimes it is handy. I was not able to find something similar in the new support classes. Finally I found a solution but it is not so straightforward as the old one (If you know better one please inform me).

In the following example I am trying to test whether my DAO is fetching addresses of the client so I do not get LazyInit exception. In my test I need to store a client to the database, finish the transaction, then load the client from the DB in a new transaction and finally check if the addresses are accessible even when no transaction is active.



01   @Test
02   @NotTransactional
03   public void testCreateAndLoadInTwoTransactions()
04   {
05     final Client client = new Client(PERSONAL_NUM);
06     client.setName("John Doe");
07     client.addAddress(new Address("Old st.","2a","Prague","120 00"));
08     client.addAccount(new Account("123-4560789"));
09     client.addAccount(new Account("888-8888888"));
10     getTransactionTemplate().execute(new TransactionCallback(){
11       public Object doInTransaction(TransactionStatus status) {
12         return clientDao.createClient(client);
13       }
14       
15     });
16     Client loadedClient = (ClientgetTransactionTemplate().execute(new TransactionCallback(){
17       public Object doInTransaction(TransactionStatus status) {
18         return clientDao.loadClientWithPersonalNumber(PERSONAL_NUM);
19       }
20     });
21     assertEquals(2,loadedClient.getAccounts().size());
22     
23     //cleanup
24     getTransactionTemplate().execute(new TransactionCallback(){
25       public Object doInTransaction(TransactionStatus status) {
26         clientDao.deleteClient(client.getId());
27         return null;
28       }
29       
30     });
31     
32   }
33   private TransactionTemplate getTransactionTemplate() {
34     return new TransactionTemplate(transactionManager);
35   }

As you can see, I have marked the method as NonTransactional. Therefore, Spring does not create a transaction for me and I can manage my transaction programmatically. For example using Spring transaction template. And that’s it.

The source code can be downloaded from SVN repository here.

Note: If you wonder why the hell I have started to write in something that looks almost like English when apparently I do even more mistakes than in Czech, the answer is simple. I just need to practice my English (apart from that I want to be world famous, not just known in Czech Republic)

Unit testy a čistota návrhu

Thursday, June 14th, 2007

I když si myslím, že mám s unit testy dost zkušeností, stále ještě mě dokáží dost překvapit. Kromě toho, že se překvapuji tím jak často je „zapomínám“ psát, překvapují mě většinou pozitivně. Zrovna nedávno mě překvapily znovu.

Psal jsem program, který mimo jiné prohledává adresář na disku, poté nalezené soubory zpracovává, přesouvá jinam, archivuje, vytváří adresáře a dělá jiné psí kusy se souborovým systémem. Tuto část jsem chtěl otestovat. Ale jak na to? Jedno z dogmat unit testů nám říká, že unit test nepracuje se souborovým systémem. Kdybych použil třídu java.io.File, nemohl bych napsat unit test, protože tato třída na disk přistupuje. Řešení je jednoduché – musel jsem abstrahovat přístup na souborový systém. Přiznávám, že se mi do toho moc nechtělo. Abstrahovat přístup, který je už abstrahován jazykem mi přišlo zbytečné. Ale nakonec jsem se odhodlal a napsal jsem si rozhraní FileSystemDao



public interface FileSystemDao {
  
  public void copyFile(File source, File destinationthrows IOException;
  public void moveFile(File source, File destinationthrows IOException;
  public File[] listFiles(File directory, String pattern);
  public boolean isDirAccessible(File directory);
  public boolean isAccessible(File file);
  public File findOldest(File[] files);
  public boolean createDir(File file);
  public boolean delete(File file);
  public InputStream getInputStream(File filethrows IOException;
  public Reader getReader(File filethrows IOException;
}

Uznávám, že nazývat to DAO je trochu zavádějící, ale podle mě to vystihuje co jsem jím chtěl vyjádřit.

Skvělé, mám DAO pro přístup do souborového systému, mohu si vytvořit mock a vesele unit testovat. Nápad je to dobrý, nicméně má své vady na kráse. Výsledný test je neuvěřitelně nepřehledný. Vytvořit mock, který simuluje stav souborového systému před operací a po ní je docela náročné. Navíc jsem přišel na to, že tento test je až moc odtržen od reality. Neupozorní nás na to, že například kopírujeme soubor do neexistujícího adresáře. Při každé změně aplikace, se navíc testy musely dost pracně aktualizovat. Vydržel jsem to den a pak jsem tyto testy smazal. Kvůli pracnosti jsem zavrhl i myšlenku na to, že bych souborový systém simuloval v paměti, například pomocí nějakého stromu. Poslechl jsem Testuvia, který říká

Nezabředni do unit testového dogmatu

Napsal jsem normální funkční test, který využívá souborový systém. S tímto testem jsem nadmíru spokojen. Probíhá velmi rychle, hledá mi chyby a není s ním žádná práce. Navíc když se na něj podívám, krásně popisuje to co má testovaný kus kódu dělat.

Ale co se stalo s rozhraním FileSystemDao? Vypadá to, že teď už není potřeba. Opak je pravdou. Hodně se osvědčilo. Je v něm kód pracující se souborovým systémem soustředěný na jediném místě. Když jsem byl donucen změnit kód pro vyhledávání souborů pomocí masky, změnil jsem ho na jediném místě. Když se přišlo na to, že před každým přesouváním souboru musím vytvořit cílový adresář, zařídil jsem to změnou na jediném místě. Když se ukázalo, že je toto řešení neefektivní, opravil jsem to pouze v implementaci tohoto rozhraní.

Vidíme tedy, že mě potřeba napsat unit test donutila abstrahovat něco co by mě vůbec abstrahovat nenapadlo. Ono totiž na první pohled není o moc lepší psát fileSystemDao.moveFile(source, dest) než source.renameTo(dest). Teprve čas ukázal, že ta první, na první pohled nesmyslná varianta má své výhody. Nezbývá mi než si zopakovat starou pravdu

Unit testy si vynucují dobrý návrh.

Cesta Testiova

Tuesday, May 29th, 2007

S vědomím toho, že zklamu své fanoušky, kteří jsou zvyklí na originální a vysoce vtipné příspěvky v mém blogu, dnes budu psát naprosto nepůvodně. Musím se s vámi podělit o nedávném archeologickém objevu. Sám jsem se o něm dozvěděl od pana Alberta Savoia.

Jde o objev vcelku velkolepý. Byly nalezeny nejstarší pozůstatky softwarového startupu. Mezi jinými artefakty byla nalezena knížečka s názvem „Cesta Testiova“. Uvedu jen výňatky, zbytek si sami určitě přečtěte v originálním překladu do angličiny.

Píšeš-li kód, piš i test
Žák se zeptal mistra programátora:
„Kdy mohu přestat psát testy?“
Mistr odpověděl:
„Až přestaneš psát kód.“
Žák se zeptal:
„Kdy přestanu psát kód?“
Mistr odpověděl:
„Až se staneš manažerem.“
Žák se zatřásl a zeptal:
„Kdy se stanu manažerem?“
Mistr odpověděl:
„Až přestaneš psát testy.“
Žák odběhl napsat nějaké testy.
Jen se za ním zakouřilo.

Pokud si kód zaslouží být napsán, zaslouží si mít testy.

Nejlepší čas na testy je, když je kód čerstvý
Tvůj kód je jako jíl. Když je čerstvý, je měkký a poddajný. Jak stárne, stává se tvrdým a křehkým.
Píšeš-li testy, když je kód čerstvý a je snadné ho změnit, testování je jednoduché. Kód i testy budou odolní.
Píšeš-li testy, když je kód starý a je těžké ho změnit, testování bude obtížné. Kód i testy budou křehcí.

Dobrý test selže
Žák přišel za mistrem programátorem a řekl:
„Všechny mé testy stále procházejí. Nezasloužím si povýšení?“
Mistr dal žákovi pohlavek a odpověděl:
„Pokud tvé testy stále procházejí, musíš psát lepší testy“
Žák s rudou tváří odběhl stěžovat si na HR.
Ale to už je jiný příběh