never in the field of software development have so many owed so much to so few lines of code
never in the field of software development have so many owed so much to so few lines of code
…if JUnit is so great then why leave it for TestNG?!
The original goal of this presentation was to persuade my teammates to switch to TestNG. :) But then I decided to publish it and to persuade you, dear reader, that TestNG is worth migrating to (especially if you still use JUnit).
Ok, I’m biased, I admit. I like TestNG, I support it, I even wrote a book about it. Yes. Still, I have some arguments in favour of TestNG, so maybe these few slides are worth reading.
My goal is to inform you about the advantages of TestNG, not to bash JUnit! I feel indebted to this great project! However, when choosing technology for my next project, I want to select the best one available. So, sorry JUnit…
My (biased) observations
People are moving from JUnit to TestNG once they start to write integration and end-to-end tests (e.g. with Selenium). Apparently, on unit tests level you can put up with JUnit deficiencies, but once you go outside the unit tests realm, JUnit stands in your way. |
While better support for integration and end-to-end tests is definitely true, not many people know that TestNG offers more than JUnit also in case of unit tests.
Ok, now I will show you some code snippets so you can judge for yourself if TestNG is worth your time.
I do not describe them in detail, please consult TestNG documentation for more information.
No need to create own Thread objects! Use annotations and that is it!
An example of running same test method simultaneously (20 times, by 3 threads):
@Test(threadPoolSize = 3, invocationCount = 20) public void concurrencyTest() { System.out.print(" " + Thread.currentThread().getId()); }
Result:
13 12 13 11 12 13 11 12 13 12 11 13 12 11 11 13 12 11 12 13
In general, it is a good practice, to test your code with different sets of parameters:
A good testing framework should make it simple, and avoid copy&paste coding.
Alas, the default JUnit implementation of the parametrized tests is rather unusable… In short, JUnit expects all test methods to be parameter-less. Because of this constraint, parametrized tests with JUnit looks awkward, because parameters are passed by constructor and kept as fields!
From what I have noticed, people who work with JUnit usually test their code with only one set of parameters. I suspect this is because parametrized tests are unusable with JUnit. And this is bad, because such tests are really useful. |
If you really, really have to use JUnit (do you?) then at least us JUnit Params! |
Parametrized tests are very simple with TestNG (no constructor awkwardness).
You can have as many data providers in one class as you wish. You can reuse them (call them from other classes), and you can make them "lazy", so each set of parameters is created when required.
Once you migrate to TestNG you will notice that you start writing a lot of parametrized tests. Because they are so very useful!
Ok, so this is how it looks with TestNG. There is a test method (shouldAddSameCurrencies()) which has some "testing logic" and a data provider method (getMoney()) which provides data. That is all.
@DataProvider private static final Object[][] getMoney(){ return new Object[][] { {new Money(4, "USD"), new Money(3, "USD"), 7}, {new Money(1, "EUR"), new Money(4, "EUR"), 5}, {new Money(1234, "CHF"), new Money(234, "CHF"), 1468}}; } @Test(dataProvider = "getMoney") public void shouldAddSameCurrencies(Money a, Money b, int expectedResult) { Money result = a.add(b); assertEquals(result.getAmount(), expectedResult); }
You also get a very detailed information about the test results (in this case thanks to reasonable implementation of Money class toString() method), e.g.:
BTW. not sure if you noticed, but parametrized tests are perfect for verifying if all your "utils" methods work - you know, all this date/strings/numbers handling/transforming methods.
TestNG does not care how many parametrized tests you have in one class. See below for an example of MoneyTest which verifies both the constructor and add() method using data providers.
@DataProvider private static final Object[][] getMoney(){ return new Object[][] { {new Money(4, "USD"), new Money(3, "USD"), 7}, {new Money(1, "EUR"), new Money(4, "EUR"), 5}, {new Money(1234, "CHF"), new Money(234, "CHF"), 1468}}; } @Test(dataProvider = "getMoney") public void shouldAddSameCurrencies(Money a, Money b, int expectedResult) { Money result = a.add(b); assertEquals(result.getAmount(), expectedResult); } @DataProvider private static final Object[][] getInvalidAmount(){ return new Integer[][] {{-12387}, {-5}, {-1}}; } @Test(dataProvider = "getInvalidAmount", expectedExceptions = IllegalArgumentException.class) public void shouldThrowIAEForInvalidAmount(int invalidAmount) { Money money = new Money(invalidAmount, VALID_CURRENCY); } @DataProvider private static final Object[][] getInvalidCurrency(){ return new String[][] {{null}, {""}}; } @Test(dataProvider = "getInvalidCurrency", expectedExceptions = IllegalArgumentException.class) public void shouldThrowIAEForInvalidCurrency(String invalidCurrency) { Money money = new Money(VALID_AMOUNT, invalidCurrency); }
TestNG offers well-thought @Before… and @After… annotations (on suite, class, group and method level).
An example below shows that a server (something heavy) is created only once before a test suite, and client (something small and cheap to create) is created before each test method.
@Test public class TestFixtureTest { private Server server; private Client client; @BeforeSuite(timeOut = 1000) public void setUpServer() { server = new Server(); server.start(); } @BeforeMethod public void setUpClient() { client = new Client(); } // some test methods here @AfterSuite public void shutDownServer() { server.stop(); } }
Want to:
It is pretty simple to pass parameters to test methods (example copied directly from TestNG documentation):
@Parameters({ "datasource", "jdbcDriver" }) @BeforeMethod public void beforeTest(String ds, String driver) { m_dataSource = ...; m_jdbcDriver = driver; }
Parameters can come for example from configured Maven Surefire Plugin.
This is also supported by tools - e.g. it is pretty simple to instruct Maven to do it (see here).
Rather bad practice with unit tests,
but very important for integration and end-to-end tests, e.g.:
Fail fast means that the feedback will be much quicker in case of failed tests.
Logical dependencies gives you a much more realistic error information - you learn that 1 tests has failed and 99 has been skipped, which is much easier to fix than the information about 100 failed tests (OMG! what’s going on!? All tests failed…. aaarrhghg!).
Example copied from TestNG documentation.
Method method1 will be skipped (will not run) if serverStartedOk fails.
@Test public void serverStartedOk() {} @Test(dependsOnMethods = { "serverStartedOk" }) public void method1() {}
Example copied from TestNG documentation.
Method method1 will be skipped (will not run) if any method of group init fails.
@Test(groups = { "init" }) public void serverStartedOk() {} @Test(groups = { "init" }) public void initEnvironment() {} @Test(dependsOnGroups = { "init.* }) public void method1() {}
It is pretty easy to write your own listeners (which act in runtime) and reporters (which are invoked after the execution).
Example output of listener which prints test result, class and method name, and execution time of each test method:
OK FirstTest fifthTestMethod 1944 OK FirstTest firstTestMethod 3799 OK FirstTest fourthTestMethod 1920 OK FirstTest secondTestMethod 4891 OK FirstTest thirdTestMethod 2963 OK SecondTest methodA 3525 OK SecondTest methodB 1390 FAILED SecondTest methodC 117 SKIPPED SecondTest methodD 23 OK SecondTest methodE 4552
This feature is not very useful with unit-tests but can be very handy to report progress/results of integration and end-to-end tests.
There are few more interesting features of TestNG (e.g. factories) but lets skip it.
(chart from Google trends)
Well, as you can see TestNG is not really closing the gap. It rather seems to find its niche and sit there. Well, it is time to change it! :)
All data gathered on 4th April 2012.
JUnit | TestNG | |
---|---|---|
releases since 2009 (not counting betas) |
3 - from 4.8.2 to 4.10 (see JUnit announcements) |
13 - from 5.12 to 6.4 (see TestNG Changelog) |
mailing list |
JUnit mailing list, |
TestNG mailing list, |
One could say, this all means only that JUnit development has been finished, all documentation has been written, all questions answered and easy to find by googling, all issues solved, … well, actually no. It rather looks, hm… a little bit abandonned.
Yes, with TestNG you still can:
Great things are done by a series of small things brought together.
It is many small things which sum up and give you a much better experience.
TestNG offers support for all kind of tests. It is great, and still getting better with each new release.
…is TestNG a silver bullet? No, but close to it. :)
P.S. Want to know more about TestNG? Read the book.