How to test non-testable code

How to test an application built on non-testable code?

If you’ve been into technology for some time, you certainly know that it’s possible to write code which is not testable. Most of our work is done with already existing code. Legacy code often does not use IoC and is not based on interfaces, and sometimes it has nothing to do with OOP. The question is: is it possible to automatically test code like this (assuming that rewriting the application is not an option)?

Below you can see a sample class which does not use Dependency Injection:


public class Example {
 
	private Dao dao;
 
	public Example() {
		dao = new Dao();
	}
 
	public int getCurrentDay() {
		return CurrentDate.getDay();
	}
}

What’s wrong with the code above? Firstly, it’s strictly related to the Dao class, secondly, it uses the static method of CurrentDate.getDay(). Changes in the Dao class or in the getDay() method of the CurrentDate class directly affect the test result. Unit tests, as the very name suggests, should test a unit (a class) and only the condition of this unit should influence the tests‘ results. If the implementation of the CurrentDate.getDay() method changes, our test will stop working.

Let’s consider another example:


public class POJO {
	private Integer id;
	private LastUpdatedDateTime lastUpdated;
	private String name;
 
	public POJO(Integer id, LastUpdatedDateTime lastUpdated, String name) {
	this.id = id;
	this.lastUpdated = lastUpdated;
	this.name = name;
	}
}
 
public class LastUpdatedDateTime { //Implementation - not interface!
	// some implementation here
}

 

The whole POJO class would be written well if not for one detail – the class is about implementation and not interface. This way, POJO directly depends on the LastUpdatedDateTime class – any modification to this class can affect the POJO class.

What to do in such a situation? Give up automatization and do only manual tests? There is another solution!

ATDD in action

If we are not able to write unit tests for methods of a given class, we can move the problem to a higher level (a common engineering practice – solving a problem by creating several others) and instead of a unit, we are going to test ready-made functionalities. To do that, we’re going to use acceptance tests.


public class CalendarTest {
private Date date = new Date();
	
1)	@Test
public void assertCurrentDateDisplayedOnHomePage() {
 
2)	Selenide.open(“url_to_page”);
 
3)	assert	$(“css_or_xpath_selector_to_displayed_date”)
.should(be(visible))
.getText
.equals(date.getDay().toString()) ;
}
}

Let’s now describe what’s happened above.

  1. Standard JUnit library annotation, marking the method as a test.
  2. Statistical method opening the browser (Mozilla Firefox by default) and going to the given URL (String). The method is waiting for the page to load completely.
  3. We can refer to every element in DOM through a selector.

What next?

Acceptance testing brings value to the project in many ways.

First of all, by using libraries such as JBehave or Cucumber (at best – Spock) we can modify them to a form which is understandable for business clients, and thus, make them familiar with the testing process (or even involve them in the process, e.g. by writing features for Cucumber).

Apart from that, new developers will find it easier to get into the project – all the functionalities will be described in detail and available in the repository.

What’s most important, testing functionalities, we move away from the code, which enables testing an app build of non-testable classes. We cannot solve the problem of a badly written application this way, but we get around it to ensure quality in our project at least in a part.

What about the test pyramid?

You might be wondering what to do with the principle saying that application quality should be based more on unit tests than functionality tests. This rule is right – a lot of things may go wrong while conducting other than unit tests: losing the connection to the database or dependence on external services. Unit tests are much quicker, too, which allows getting more immediate feedback. The problem is that bad code can be written regardless of anything (even in Java, which is quite idiot-proof);  bad code meaning code which can’t be tested in an optimum manner. I think that it’s better to have any (well written!) automatic tests than have none at all. Besides, sometimes you have to convince the client to write unit tests (and, which usually follows, to refactoring), and that – let’s be honest – is rather difficult.

Summary

If you’re working with spaghetti code and you’re facing the problem of quality assurance, it’s better to use acceptance tests, which check the functionality of the tested system from the end user’s perspective. With an adequate number of well-written tests, you will avoid regression and you will automatize a grand part of your team’s work. Remember also that constant dropping wears away a stone: after some time, your client may see the value of such tests and may become convinced to invest time and money in unit tests, which are the basis of every well-written application.


Here are the libraries and frameworks that we use at XSolve:

 

Written on 28 July 2016 by

Tomasz Czermiński

QA Engineer and Java programmer (who knows Groovy as well). He's responsible for automatization of a testing process. When programming always looking for an essence of application that is tested with a focus on features that are the most important from the business point of view. His passions are mathematics, algorithms, and African culture.

QA Engineer and Java programmer (who knows Groovy as well). He's responsible for automatization of a testing process. When programming always looking for an essence of application that is tested with a focus on features that are the most important from the business point of view. His passions are mathematics, algorithms, and African culture.

This site uses cookies. By continuing to browse the site you are agreeing to our use of cookies.

To find out more click here

Ok, Thanks