How to test an application built on untestable code?
If you’ve been into programming long enough, you certainly know that writing an untestable code happens from time to time. Most of our work is done with code that already exists. The question is wheather we can automatically test code like this (assuming that rewriting the application is out of discussion)?
Below you can see a sample class that 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? First of all 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 result of the test. Unit tests, as the very title 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 shifts, our test will stop working.
Let’s take a look at 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 correct if not for one detail – the class is about implementation and not interface. This way, POJO directly depends on the LastUpdatedDateTime class – any change to this class can affect the POJO class.
What do you do in a situation like that? Give up automatization and perform only manual tests? There is a solution!
ATDD in actionIf we can not 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, test ready-made functionalities. To do that, we have 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 us now discuss what happened above.
- Standard JUnit library annotation, marking the method as a test.
- Statistical method opening the browser (Mozilla Firefox by default) and going to the given URL (String). The method waits for the page to load completely.
- We can refer to each and every element in DOM through a selector.
Acceptance testing brings value to the project in many different ways.
Firstly, by using libraries such as JBehave or Cucumber (at best – Spock) we can modify them to a form which is coherent for business clients, and thus, make them familiar with the testing process (or even engage them in the process, e.g. by writing features for Cucumber).
What is more, new developers will find it easier to dive into the project – all the functionalities will be described in detail and available in the repository.
What’s most important, testing functionalities, we shift from the code, which enables testing an app build of untestable classes. We cannot solve the problem of a poorly written application this way, but we get around it to partialy ensure the quality of our project at least.
How about the test pyramid?You must be wondering what about the principle saying that application quality should be based more on unit tests rather than functionality tests. This rule was astablished not without a reson – a lot of things can go wrong while conducting other than unit tests: losing the connection to the database or dependence on external services. Unit tests are much quicker and provide more immediate feedback. Unfortunately bad code is produced on daily basis regardles of anything (even in Java, which is quite idiot-proof); bad code meaning code which can not be tested in an optimum manner. In my opinion it’s better to have any (well written!) automatic tests than have none at all. Besides, sometimes there is a necessity to convince the client to write unit tests of their won (and, which usually follows, to refactoring), and that – let’s be honest – is rather difficult.
SummaryWhen you work with spaghetti code and you face the problem of quality assurance, it’s better to use acceptance tests because they check the functionality of the system being tested from the end user’s perspective. With a proper number of well-written tests, you will avoid regression and automatize a substantial part of your team’s work. Bear in mind that constant dropping wears away a stone. Over time your client may see the value of such tests and convince themselves to invest more time and money in unit tests, which are the footing of a every well-written application.
Writter: Tomek QA Engineer and Java programmer at xSolve
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.