Mocks are used to “fake” the behavior of objects to the extent required by tests. Most of the time, they can be pretty simple: when calling a method with given set of (constant) argument, then return some predefined value. Mocking frameworks actually strive to translate the previous sentence directly into code, which can be seen in Java’s Mockito library:
What is evident here is that this mock doesn’t maintain any state. As a result, all stubbed method are completely independent and referentially transparent: they always return the same value for the same set of arguments. In reality, that property is even stronger because they are simply constant.
Simple mocks like those work for a good number of cases. Some may even argue that it’s the only way to mock properly, because anything more complicated is a sign of code smell. We can counter that by saying that some complexity is often unavoidable, and it’s up to developers to decide whether they want to put it test or tested code.
And thus more complex stubbing may be needed if, for some case, you prefer the former approach. When that happens, it’s good to know how to fabricate a bit more sophisticated behavior. In Mockito, it’s very possible to achieve almost arbitrary complexity by capturing arguments of stubbed calls and routing them to callbacks. This is done with the help of ArgumentCaptor
class and the Answer
interface, respectively.
When combined, they can result in pretty clever mocks, as illustrated with this example of some imaginary API for SMTP (e-mail sending protocol):
Note that it doesn’t implement the real SMTP. That, presumably, would mimic the SmtpConnection
class exactly, which of course is not the point of mocking at all.
The point is to provide sufficiently complete behavior so that code under test can proceed, reaching all the execution paths we want to exercise. In this case, something can call getOutgoingClientData
and rely on certain string being present in the output.
Let’s just hope that this “something” actually has some good reasons to expect that…
Writing code is not everything there is in programming. But writing code comprises of much more than just typing it in. There is compiling or otherwise building it; running the application to see whether it works how it breaks; and of course debugging to pinpoint the issue and fix it. These are inherent parts of development process and we shouldn’t expect to be skipping them anytime soon…
Well, except that right now, I virtually pass on all of them. Your mileage may vary, of course, but I wouldn’t be surprised if many more developers found themselves in this peculiar position. I actually think this might be a sign of times, and that we should expect more changes in developer’s workflow that head in this very direction.
So, how do you “develop without developing”? Let’s look at the before mentioned activities one by one.
Getting rid of the build step is not really inconceivable. There are plenty of languages that do not require additional processing prior to running their code. They are called interpreted languages, and are steadily gaining grounds (and hype) in the programming world for quite some time now.
Python, JavaScript and Ruby are probably the most popular among them. Given that I’m currently doing most of my development work in the first two, it’s no wonder I don’t find myself compiling stuff all that often.
But even if we’re talking about traditional, de facto compiled languages (like Java or C++), there’s still something missing. It’s the fact that you don’t often have to explicitly order your IDE to compile & build your project, because it’s already doing it, all the time.
I feel there’s tremendous productivity gain by shortening the feedback loop and having your editor/IDE work with you as your write the code. When you can spot and correct simple mistakes as you go, you end up having more time and cognitive power for more interesting problems. This background assistance is something that I really like to have at all times, therefore I’ve set it up in my editor for Python as well.
The kind of programs I’m writing most often now – server-side code for web applications and backends – does not require another, seemingly necessary step all that often: running the app. As it stands, their software scaffolding is clever enough to detect changes in runtime and automatically reload program’s code without explicit prompting.
Granted, this works largely because we’re talking about interpreted languages. For compiled ones, there are usually many more hurdles to overcome if we want to allow for hot-swapping code into and out of a running program. Still, there are languages that allow for just that, but they are usually chosen because of reliability requirements for some mission critical systems.
In my opinion, there are also significant programming benefits if you can pull it off on your development machine. They are again related to making the cycle of writing code and testing it shorter, therefore making the whole flow more interactive and “real-time”. As of recently, we can see some serious pushes into this very direction. Maybe we will see this approach hitting mainstream soon enough.
“Oh, come on”, you might say, “how can you claim you’ve got rid of debugging? Is all your code always correct and magically bug-free?…”
I wish this was indeed true, but so far reality refuses to comply. What I’m referring to is proactive debugging: stepping though code to investigate the state of variables and objects. This is done to verify whether the actual control flow of particular piece of code is the one that we’ve really intended. If we find a divergence, it might indicate a possible cause for a bug we’re trying to find and fix.
Unfortunately, this debugging ordeal is both ineffective and time consuming. It’s still necessary for investigating errors in some remote, test-forsaken parts of the code which are not (easily) traceable with other methods and tools. For most, however, it’s an obsolete, almost antiquated way of doing things. That’s mainly because:
assert
that fails), while the relevant part of the code is even easier to localize. You might occasionally drop into debugger to examine local variables of the test run, but you never really step through whole algorithms.It’s not like you can throw away your Xdb completely. With generous logging, decent test coverage and a little cautiousness when adding new things, the usefulness of long debugging sessions is greatly diminished, though. It is no longer mandatory, or even typical part of development workflow.
Whatever else it may be, I won’t hesitate calling it a progress.
When discussing the topic of unit testing and methodologies they might entail (mostly TDD, i.e. Test-Driven Development), I noticed a curious imbalance in the number and strength of arguments pro and contra. The latter are few and far between, up to the point of ridiculous scarcity when googling “arguments against TDD” is equally likely to yield stories from both sides of the fence. That’s pretty telling. Is it so that TDD in general and unit tests in particular are just the best thing ever, because there is an industry-wide consensus about them?…
I wouldn’t be so sure. All this unequivocal acknowledgement looks suspiciously similar to many other trends and fashions that were (or still are) sweeping through the IT domain, receding only when the alternative approach gains enough traction.
Take OOP, for example. Back in the 90s and around 2000, you would hear all kinds of praise for the object-oriented methodology: how natural it is, how it helps to model problems in intuitive way, how flexible and useful its abstractions are. Critics’ camp existed, of course, but they were small, scattered and not taken very seriously. Objects and classes were reigning supreme.
Compare this to present day, when OOP is taking blows from almost every direction. On one hand, it is rejected on performance basis, as the unknown factors of virtual method’s call are seen as a liability. On the other hand, its abstraction patterns are considered baroque, overblown and outdated, unfit for modern computing challenges – most notably concurrency and asynchronism.
Could it be that approaches emphasizing the utmost importance of unit tests are following the same route? Given the pretty much universal praise they are receiving, it’s not unimaginable. In this context, providing some reasonable counterarguments seems like a good thing: if we let some air out of this balloon, we may prevent it from popping later on.
Incidentally, this is a service for TDD/unit testing that I’m glad to provide ;-) So in the rest of this post, I’m going to discuss some of their potential drawbacks, hopefully helping to even-out the playing field. Ultimately, this should always lead to better software engineering practices, and better software.
Niskopoziomowe, wewnętrzne klasy logiki mają tę wadę (jeśli można to tak nazwać…), iż są właśnie niskopoziomowe i wewnętrzne – a przez to trudne do testowania w powiązaniu z całą aplikacją. Między nimi a interfejsem może znajdować się wiele warstw, które utrudniają debugowanie.
To sprawia, że przydatne stają się testy jednostkowe (unit tests). Do ich tworzenia i uruchamiania potrzeba jednak odpowiednich frameworków. Na szczęście te są już dostępne dla niemal każdego sensownego języka programowania i nierzadko są wręcz częścią jego lub jego środowiska.
Nie zawsze jednak tak było. Alternatywą dla ręcznego tworzenia specjalnych aplikacji przeznaczonych wyłącznie do testowania klas były (i właściwie nadal są) interesujące mechanizmy “uruchamiania klas” jako takich, które oferują niektóre języki programowania.
Dotyczy to przede wszystkim – jeśli nie wyłącznie – tych spośród nich, w których koncepcja programu czy aplikacji nie jest wyraźnie zakreślona. Coś jakiego jak entry point (“punkt wejścia”), od którego zaczyna się wykonywanie kodu, musi jednak istnieć i trzeba mieć jakiś sposób na jego określenie. Jeżeli zamiast aplikacji mamy tylko mniej lub bardziej luźny zbiór klas, to siłą rzeczy musi się on znajdować w którejś z nich.
A jeśli znajduje się w jednej, to czemu nie dodać go też do innych – także tych, które z założenia nie mają pojęcia o interakcji z użytkownikiem? Tworzymy w ten sposób pewnego rodzaju back-end, a owe dodatkowe punkty wejścia mogą posłużyć do testów. Oto prosty przykład w języku Java:
public class Sumator
{
private int sum = 0;
public void add(int x) { sum += x; }
public int getSum() { return sum; }
// testowy punkt wejścia
public static void main(String[] args) {
int n = args.length > 1 ? Integer.parseInt(args[1]) : 100;
Sumator s = new Sumator();
for (int i = 1; i <= n; ++i) s.add(i);
System.out.println ("Spodziewana suma: " + (n * (n + 1) / 2));
System.out.println ("Otrzymana suma: " + s.getSum());
}
}[/java]
Statyczna metoda main
to w Javie sposób na określenie punktu wejścia. Typowym miejscem dla niego jest główna klasa aplikacji, względnie całkiem osobna klasa przeznaczona do zarządzania samym uruchamianiem programu. Tutaj umieszczamy metodę main
w klasie logiki (bardzo zaawansowanej zresztą ;-]), przez co możemy “odpalić” samą tę klasę i wykonać jakiś kod testujący jej funkcjonalność.
Z popularnych języków mających taki feature można jeszcze wspomnieć Pythona. W nim obiektowość nie jest obowiązkowa, więc to skrypt (plik .py) jest podstawową jednostką kodu, którą można uruchamiać:
Daje się w nim też umieścić “swobodny” kod, co na pierwszy rzut oka wydaje się dobrym miejscem na instrukcje testowe. Trzeba tylko otoczyć je pokazanym wyżej if
em, aby były one uruchamiane wyłącznie przy wywoływaniu skryptu z wiersza poleceń, nie zaś przy imporcie zawartego w nim kodu (instrukcją import
).
Poprzednia notka o “rootkitach i innych nieszczęściach” zdołała utrzymać się długo, więc mogła stworzyć wrażenie, że cały czas źle się u mnie dzieje :) Nieprawda to oczywiście, więc wypadałoby w końcu coś na tę dezinformację poradzić.
Ostatnio cierpię na niewielki brak czasu spowodowany koniecznością dokładania nieco poważniejszych wysiłków co do pewnego dzieła, które docelowo ma zapewnić mi otrzymanie tytułu inżyniera informatyki. Z pewnych względów nie zdradzę, czym ono jest, ale pozwolę sobie wspomnieć o pewnych wnioskach, które przy okazji mi się nasunęły.
Parę miesięcy temu wypowiadałem się pochlebnie na temat testów jednostkowych, mając wtedy raczej niezbyt duże doświadczenie w ich stosowaniu. Dzisiaj jest już ono zdecydowanie większe i w związku z tym stwierdzić muszę, że… Nie, nie chcę powiedzieć, że wtedy myliłem się całkowicie ;P Chodzi raczej o równowagę zalet i wad.
To prawda, że unittesty dobrze sprawdzają się jako narzędzie znajdowania błędów i zapobiegania im. Do pewnego stopnia pomagają też w poprawianiu interfejsu tworzonych klas, tak aby ich użycie było wygodniejsze i bardziej intuicyjne. Te korzyści mają jednak swoją cenę, którą jest przede wszystkim czas poświęcony na pisanie owych testów – zwłaszcza w niektórych metodykach wytwarzania oprogramowania, które kładą na nie ogromny nacisk. Całkiem normalna jest bowiem sytuacja, że kod testowy jest dłuższy niż testowany (czasem nawet kilkakrotnie). Dodatkowo nie mamy przecież znikąd żadnej gwarancji, że ów testowy kod sam nie zawiera jakichś błędów… W sumie więc dochodzi nam sporo dodatkowej pracy w zamian za większą niezawodność tworzonego oprogramowania.
Drugi wniosek jest bardziej – że tak powiem – filozoficzny :) Otóż programowanie wedle zasady:
może i jest efektywne, ale na dłuższą metę zaczyna się robić nużące i trochę zniechęcające. Pomyślmy jak łatwo jest pisać kod “rozgrzebany”, którego nie planujemy od razu kompilować i poprawiać tych wszystkich pomyłek, których kompilator nam zasygnalizuje. Porównajmy to z pisaniem kodu, który nie tylko jak najczęściej kompilujemy, ale i regularnie przepuszczamy przez własnoręcznie napisane testy! Toż to straszne, tak samemu kręcić na siebie bat :) Gdzie tu koderska wolność artystyczna? :D
Ale nie ma rady, czasami trzeba zakasać rękawy i testować…
Kod skompilowany nie musi być kodem działającym poprawnie – tę prawidłowość zna każdy programista. Z drugiej jednak strony dogłębne przetestowanie programu jako całości to proces żmudny, trudny, niepokojący i obarczony sporym ryzykiem niepowodzenia. A przy tym oczywiście konieczny. Można go jednak nieco usprawnić poprzez ograniczenie możliwości występowania błędów w niektórych częściach programu – takich, które wcześniej potraktujemy testami jednostkowymi (unit tests).
Ten rodzaj testów w skrócie polega na tym, aby każdy napisany element kodu – co zależnie od języka i sposobu programowania może oznaczać np. funkcję lub klasę – opatrzyć jedną lub kilkoma procedurami testującymi. Mają one na celu sprawdzanie, czy ów element robi dokładnie to, co założyliśmy, poprzez użycie go “na zewnątrz”, w innym kodzie. Innymi słowy, taka procedura testowa ma na celu uruchomienie funkcjonalności jakiejś klasy/funkcji/itp. i sprawdzenie, czy rezultaty jej działania są zgodne z oczekiwanymi.
Takie procedury można pisać po zakończeniu implementacji testowanego elementu. Niektóre metodyki tworzenia oprogramowania zalecają jednak, aby… najpierw pisać kod testowy, a dopiero potem ten właściwy! Wbrew pozorom ma to całkiem spory sens. Jeśli bowiem zaczniemy od kodu, który ma naszej klasy używać, to istnieje większa szansa, że od początku wyposażymy ją w interfejs, którego rzeczywiście da się używać bez większych problemów. Na koniec zaś będziemy dysponowali też przykładowym kodem, obrazującym sposób korzystania z danej klasy, co zazwyczaj bywa przydatne.
Według mnie największą zaletą testów jednostkowych jest to, że przy pomocy odpowiednich narzędzi możemy je bardzo szybko napisać w postaci nadającej się do natychmiastowego uruchomienia. Brak konieczności tworzenia nowej aplikacji – dla samych tylko testów – jest niezwykle wygodny. Najczęściej bowiem jedyne, co musimy zrobić, to napisać klasę z procedurami testowymi i oznaczyć ją odpowiednio w kodzie, a następnie po prostu uruchomić testy (co często daje się zautomatyzować jako czynność następną po kompilacji) i obserwować wyniki.
Szczegóły mogą się aczkolwiek różnić w zależności od języka, jako że frameworków dla testów jednostkowych jest sporo. Mamy na przykład:
Tak naprawdę to właściwie dla każdego liczącego się języka istnieje narzędzie tego typu. Polecam więc przyjrzenie się im (i całemu zagadnieniu testów jednostkowych), jeśli wcześniej nie mieliśmy z tym tematem styczności. Bo przecież testowania nigdy dość ;)