Posts Tagged TDD

To unit test or not to?

Once  a team of developers, who created a pretty much complete set of automated functional tests for their application, asked me how they could possibly benefit from writing unit tests too. Some of them claimed that there is no need for any other type of tests as the automated suite they run could perfectly verify if the application matched client’s requirements.

So why should we even bother?

So let’s imagine you’re building a swimming pool in your backyard. You’ve already dug a hole. Now let’s suppose that you use bricks, tiles and some glue to finish the job. You don’t really care about the fact each brick has a different shape. You are also OK with the fact that some tiles are broken and half of them are just a bit thinner the the rest.
So you start building the walls first. It goes quickly. Even though the bricks are not rectangular, you can always fill the opening that tend to appear every now and then with some clay and the stones you found in your garden last week. Then you put the tiles. It’s not that easy any more as apparently the walls are not that flat and smooth. Fortunately, you’ve a lot of glue to to fix this and what’s more important you don’t really care. You want your kids to have fun as soon as possible. And finally, you complete your work. Soon the swimming pool is ready and full of water.
You try it out, and it works!

And it really does, the swimming pool will not leak in 10 years. The walls are a bit curved, but nobody can see it when the pool is filled with water. Job done.

So do you need to check if every single brick has the same dimensions as the other ones.  Do you care if their faces are rectangular?  Do you need to unit test the tiles too?

I’ll not answer this here. I’ll just ask some more questions instead 🙂

  • Would you build your house the same way?
  • Would you build a treehouse for your kids in a similar manner?
  • Would it be possible to reuse the bricks (used for the swimming pool construction) to pave your drive when you decide to do so?
  • And a bonus one: would you build a shed for your tools behind the garage of your summer house?

 

,

Leave a comment

Unit tests are your safety net

I recently came across this blog post on automated unit testing. This time I was really curious about the opinions on the topic. And quite not surprisingly it turns out there are two groups of people with two totally distinct attitudes to unit testing. There are the lovers and the haters.

What struck me, though, is that neither of them pointed the real value that is brought by a set of unit tests to a piece of software. As the examples given by the author are written in Python (which is not a statically typed language) somebody pointed out that the most of the issues would never happen if a statically typed language was used. And they were just a bit mistaken: you would run into the same problems, but you would detect them so early that you would never consider them as real issues.

And that’s what unit testing (or a fast set of automated tests) is made for. The main goal of unit testing is to provide a set of rules that are checked at compile-time (I’d call it ‘test-time’, still it should always be as close a possible to compile-time). A set of unit tests should play a role of a safety net that finds out all pieces of code that do not comply with the defined rules. And this should be done as soon as possible. Ideally, it should be as quick as the statically typed languages perform their type verifications.

And thanks to unit tests you can define any set of rules you like.

,

Leave a comment

Komarro released!

The first release o Komarro is out. The version 1.0 has been synced to Maven Central and now it is available to all maven users.

<dependency>
   <groupId>com.googlecode.komarro</groupId>
   <artifactId>komarro</artifactId>
   <version>1.0</version>
   <scope>test</scope>
</dependency>

Leave a comment

Mockarro changes name to Komarro, Komarro&Mockito

As Mockarro was not the prettiest name it has been changed to Komarro. I also cut out the Injection Point class from the SPI so that API stays as minimal as possible. According to the old saying: it’s much easier to add features to an API when they are demanded, than to remove them when it turns out they should not be there. During the last review I also realized that a possibility o defining a custom injection strategy has to be more flexible than it was.

I also created another example of ‘Komarro with Mockito’ usage.

Currently Komarro offers a possibility of defining indirect inputs to a tested method. Sometimes however, it is necessary to verify the indirect output parameters or to verify that an interaction with a collaborator took place.
As Komarro is built upon Mockito they play together quite smoothly.

Let’s consider a local Pizza delivery service. In the example below you can see both the elements of Komarro and Mockito APIs. The interaction verification will be done on the methods of the OrderRepository. That is why it is annotated by the Mockito @Mock annotation. And then initialized using the MockitoAnnotations.initMocks(this) method.
Also it is important to notice that the mocks created by Mockito can be passed to Komarro’s instanceForTesting initialization method. Here, the MockitoMockDescriptionCreator.annotatedMocks convenience method is used to automatically detect the mocks marked with the @Mock annotation.

	@Mock
	private OrderRepository orderRepository;

	private OrderService orderService;

	@Before
	public void init() {
		initMocks(this);

		orderService = instanceForTesting(OrderService.class,
				annotatedMocks(this));
	}

Then we are set and ready to write the actual test method:

	@Test
	public void testPlaceOrderSavesOrder() {
		// given
		given(Pizza.class).isRequested().thenReturn(new Pizza("Margharita"));
		given(Order.class).isRequested().thenReturn(newOrderWithId(17L));

		// when
		long orderId = orderService.placeOrder("margharita", new Address(
				"Embarcadero Rd 123", "Isla Vista", "93117"));

		// then
		verify(orderRepository).save(any(Order.class));
		assertThat(orderId).isEqualTo(17L);
	}

Note that the method uses the Komarro’s idiom to define the behavior of the collaborators and Mockito’s verify method to verify the interaction has taken place.
Komarro can be complemented with Mockito’s functionality every time it is necessary. All the verifications have to be done by Mockito, as Komarro does not provide any way to do this yet. But also, if it is happens that Komarro style definition does not provide a good definition of the indirect inputs, Mockito should be used.

The method under test could look like this:

	@Inject
	private OrderRepository orderRepository;

	@Inject
	private PizzaService pizzaService;

	public long placeOrder(String pizzaName, Address address) {
		Pizza pizza = pizzaService.getByName();

		if (pizza != null) {
			Order order = new Order();
			order.setAddress(address);
			order.setPizza(pizza);

			Order newOrder = orderRepository.save(order);

			return newOrder.getId();
		} else {
			throw new IllegalArgumentException("The pizza " + pizzaName
					+ " does not exist in the menu card");
		}
	}

The source code of the examples posted here (and some more tests) can be found at github: https://github.com/marekdec/komarro-example-pizza-shop. The important phases of the development have been tagged.

, , ,

5 Comments

Mockarro TDD example

Since the day I came up with  Mockarro I find it very hard to evaluate clearly its usefulness. I guess it’s always pretty hard to make a clear judgement on an idea when the border between its advantages and disadvantages is fuzzy. It’s probably even harder if the idea is yours.

Anyhow, I recently decided to bring Mockarro closer to its first release and to expose it to the outer world.

It is important to remember that Mockarro provides a way of defining the indirect inputs to the tested method. It trades the specification of ‘how’ the indirect inputs are obtained for ‘what’ the indirect inputs are.

Let’s start with a simple TDD example. We are going to create a part simple application that searches the database of planets that are possibly inhabitable. The whole application is supposed to be created in a top-down manner. We will focus first on finding a planet whose radius is most similar to the Earth’s mean radius.

	@Test
	public void retrievePlanetWithRadiusMostSimilarToEarthsTest() {
		// given
		Planet earth = planet("earth", 6371);
		given(Planet.class).isRequested().thenReturn(earth);

		given(new TypeLiteral<List<Planet>>() {}).isRequested().thenReturn(
				asList(earth, planet("Mars", 3396), planet("Tatooine", 55000),
						planet("Arrakis", 10123), planet("Solaris", 12700)));

		// when
		Planet planet = planetService
				.retrievePlanetWithRadiusMostSimilarToEarths();

		// then
		assertThat(planet).isNotNull().isNotSameAs(earth);
		assertThat(planet.getName()).isEqualTo("Mars");
	}

The code above defines the expected behavior of the retrievePlanetWithRadiusMostSimilarToEarthsTest method. The fixture set-up section (the given section) defines the indirect inputs to the method under test. As opposed to the standard Mocking frameworks like Mockito or !EasyMock, Mockarro does not couple the test method to collaborators within the given section. It does not define how the indirect inputs will be provided. It does, on the other hand, clearly specify what the indirect inputs the method under test are.
The method that is going to be implemented, briefly, will be given a list of all planets in the database and an instance of the planet Earth. It is required to find a planet whose radius is most similar Earth’s radius, but at the same time it is required not to return the Earth as the result.

A possible implementation of the method could use a PlanetRepository to get both: the instance of a planet Earth and a list of all planets available for the application.

	@Inject
	private PlanetRepository planetRepository;

	public Planet retrievePlanetWithRadiusMostSimilarToEarths() {
		Planet earth = planetRepository.getByName("earth");

		if (earth != null) {
			double earthRadius = earth.getKilometersOfRadius();

			Planet mostSimilarPlanet = null;
			double smallestDiff = Double.POSITIVE_INFINITY;
			for (Planet planet : planetRepository.getAllPlanets()) {
				if (planet != earth) {

					double diff = Math.abs(planet.getKilometersOfRadius()
							- earthRadius);
					if (diff < smallestDiff) {
						smallestDiff = diff;
						mostSimilarPlanet = planet;
					}
				}
			}
			return mostSimilarPlanet;
		} else {
			throw new IllegalStateException(
					"No earth in the planet repository");
		}
	}

Let’s assume that at some stage of the application life cycle a SolarSytemRepository is created. The Earth will be obtained directly from the new service. The input parameters will not change whatsoever.


	@Inject
	private PlanetRepository planetRepository;

	@Inject
	private SolarSystemService solarSystemService;

	public Planet retrievePlanetWithRadiusMostSimilarToEarths() {
		Planet earth = solarSystemService.getEarth();

		if (earth != null) {
			double earthRadius = earth.getKilometersOfRadius();

			Planet mostSimilarPlanet = null;
			double smallestDiff = Double.POSITIVE_INFINITY;
			for (Planet planet : planetRepository.getAllPlanets()) {
				if (planet != earth) {

					double diff = Math.abs(planet.getKilometersOfRadius()
							- earthRadius);
					if (diff < smallestDiff) {
						smallestDiff = diff;
						mostSimilarPlanet = planet;
					}
				}
			}
			return mostSimilarPlanet;
		} else {
			throw new IllegalStateException(
					"No earth in the planet repository");
		}
	}

This refactoring does not require the test method to be changed. The input parameters stayed the same (both direct and indirect). It has to be pointed out though, that the test method will have to be changed every time the set of the indirect input parameters is modified.

The source code of the examples in this post can be found at https://github.com/marekdec/planetary-system. The interesting stages of the development process have been tagged, navigate to https://github.com/marekdec/planetary-system/tags to find them.

, ,

4 Comments

%d bloggers like this: