Przypowieść świąteczna

Dawno, dawno temu na stercie żył sobie Obiekt Pewnej Klasy (OPK). OPK był bardzo smutny, gdyż nie miał kolegów z którymi mógłby się wymieniać danymi i bawić w gry synchronizacyjne. Dlatego też OPK prosił Programistę:

  • – Programisto stwórz mi kolegę bym mógł z nim pracować i się bawić po pracy

Programista widząc, że OPK chce dobra usłuchał jego prośby. Stworzył Kolejny OPK i go zainicjował po czym wrócił do do picia piwa i oglądania filmików.

OPK i KOPK dość szybko stwierdziły, że do brydża trzeba czterech, a potem już we czwórkę, że piłka kopana to co najmniej 22 uczestników. I tak oto O-tyPK mnożyły się i zapełniały RAM i zabierały procesor. Koniec końców, gdy skończyło się piwo, a filmiki z powodu braku zasobów komputera drastycznie zwolniły Programista zajrzał na stertę, zasmucił się i rzekł:

  • – Dlaczego żeś mi to uczynił OPK?
  • Na co OPK odpowiedział
  • – Bo mnie nie przetestowałeś idioto…
  • – Oż w żopu… ale i tak masz przestane, bo będziesz Singletonem :D- rzekł Programista i zabrał się do przerabiania OPK.

Programista kodował całą noc, refaktoryzował kod i przebudowywał referencje. Jednak pamiętając, że obiekt smutny będzie, gdy nie będzie mógł grać w ukochaną synchronizację stworzył mu pola finalne by już nigdy nie pragnął synchronizacji. Po czym powołał OPK do życia i rzekł:

  • – Za karę, że mnożyłeś się bez opamiętania uczyniłem Cię Singletonem. nigdy już nie będziesz pragnął grać w Synchronizację. Jeszcze Cię tylko przetestuję.
  • OPK popatrzył na siebie i rozradowany rzekł:
  • – A Chuja se przetestuj mondziole… Mam pola final i private co raz utworzone nie może być zmienione więc mnie nie przetestujesz, bo konfigurację dostarczasz tylko raz w konstruktorze.

To powiedziawszy OPK dumny z siebie zasiadł w statycznej pamięci.

Problem

Czego uczy nas ta przypowieść? Otóż Singleton jest bardzo dobrym wzorcem wszędzie tam gdzie wiemy, że nie potrzeba więcej niż jednego obiektu danej klasy oraz, wtedy gdy chcemy zaoszczędzić zasoby. Jednakże singletony ciężko się testuje. Dlaczego? Przyjrzyjmy się tej klasie:

Listing 1. Klasa Singleton z polem prywatnym i finalnym

package eu.runelord.blog;

public class Singleton {
	private static Singleton INSTANCE;

	private final String name;

	public static Singleton getInstance() {
		if (INSTANCE == null)
			INSTANCE = new Singleton();

		return INSTANCE;
	}

	private Singleton() {
		this.name = null;
	}

	public String toString() {
		return name;
	}
}

Mamy tu typowy problem. Pole name jest prywatne i finalne jednocześnie. Może się to wiązać z np. odczytem konfiguracji tego pola z pliku properties przy starcie aplikacji i chęci zagwarantowania jej niezmienności. Pytanie brzmi jak przetestować taki obiekt by móc wgrać kilka różnych konfiguracji? Najprościej jest zmienić modyfikatory pól na czas testów. Pomysł głupi ponieważ utrudnia automatyzację testów. Pomysł drugi wykorzystać oddzielne ClassLoadery dla każdego testu. Nieźle, ale trzeba dość dużo pisać w dodatku można popełnić masę błędów. Trzecia metoda użyć mechanizmów Reflection API.

Moja propozycja jest trochę inna. Skoro singleton pozwala na konfigurację z pliku wskazanego przez programistę to wystarczy w każdym teście podawać inny plik i po zakończeniu nullować pole przetrzymujące referencję do naszego obiektu. Przykładowa aplikacja:

Listing 2. Aplikacja która umie nullować Singleton

package eu.runelord.blog;

import java.lang.reflect.Field;

public class App {
	public static void main(String[] args) throws Exception {
		Singleton singleton = Singleton.getInstance();
		System.out.println(singleton);

		Class singletonClass = Singleton.class;
		Field fieldName = singletonClass.getDeclaredField("name");

		fieldName.setAccessible(true);
		System.out.println(fieldName.get(singleton));
		fieldName.set(singleton, "dupa");
		System.out.println(fieldName.get(singleton));

	}
}

Kluczowym elementem całej operacji jest umożliwienie dostępu do pola INSTANCE za pomocą metody setAccessible().

Metoda jest bardzo prosta i przy skomplikowanych testach może się nie sprawdzać, ale na początek wystarczy.