test-driven development in clojure - diva portal806620/fulltext01.pdf · degree project, in...

60
DEGREE PROJECT, IN , SECOND LEVEL COMPUTER SCIENCE STOCKHOLM, SWEDEN 2015 Test-Driven Development in Clojure AN ANALYSIS OF HOW DIFFERENCES BETWEEN CLOJURE AND JAVA AFFECTS UNIT TESTING AND DESIGN PATTERNS NICLAS NILSSON KTH ROYAL INSTITUTE OF TECHNOLOGY SCHOOL OF COMPUTER SCIENCE AND COMMUNICATION (CSC)

Upload: vomien

Post on 31-Jul-2018

221 views

Category:

Documents


0 download

TRANSCRIPT

DEGREE PROJECT, IN , SECOND LEVELCOMPUTER SCIENCESTOCKHOLM, SWEDEN 2015

Test-Driven Development in ClojureAN ANALYSIS OF HOW DIFFERENCESBETWEEN CLOJURE AND JAVA AFFECTSUNIT TESTING AND DESIGN PATTERNS

NICLAS NILSSON

KTH ROYAL INSTITUTE OF TECHNOLOGY

SCHOOL OF COMPUTER SCIENCE AND COMMUNICATION (CSC)

Test-Driven Development in Clojure(Testdriven utveckling i Clojure)

An analysis of how di�erences between Clojure and Java a�ects unit testing and

design patterns

(En analys av hur skillnader mellan Clojure och Java påverkar enhetstestning och

designmönster)

NICLAS NILSSON

Master’s Thesis in Computer Science at CSC

E-mail: [email protected]

Supervisor: Anders Lansner

Examiner: Jens Lagergren

March 2015

Abstract

Agile methods and Test-Driven Development are well es-tablished methodologies within the software developmentindustry. As a large part of today’s software development isdone in Object-Oriented languages like Java it’s only nat-ural that agile best practices have evolved to fit into theObject-Oriented paradigm. Clojure is a relatively youngprogramming language that greatly di�ers from Object-Oriented languages and it’s thus not certain that these bestpractices can be directly applicable in Clojure development.

This thesis attempts to identify key di�erences betweenClojure and Java that may a�ect unit testing and the Test-Driven Development process. It finds that although thetwo languages are fundamentally the di�erence betweenthem in regards to unit test creation and execution is small.The most striking consequence of Clojure’s lack of Object-Orientation is that dependency injection must be done be-tween functions rather than between classes and objects.It’s also argued that the relative immaturity of the avail-able Clojure development tools can have negative e�ects onthe Test-Driven Development process.

Referat

Agila metoder och Testdriven Utveckling är väletablerademetoder inom mjukvaruindustrin. Då en stor del av dagensmjukvaruutveckling sker inom Objektorienterade språk somJava är det bara naturligt att agila best-practices har ut-vecklats för att passa den Objektorienterade paradigmen.Clojure är ett relativt ungt programmeringsspråk som skil-jer sig kraftigt från Objektorienterade språk och det är där-för inte självklart att dessa best-practices kan vara direktapplicerbara på utveckling i Clojure.

Denna uppsats försöker identifiera de huvudskillnadermellan Clojure och Java som kan ha en direkt påverkanpå enhetstestning och den Testdrivna Utvecklingsproces-sen. Man upptäcker att det - de två språkens fundamentalaskillnader till trots - endast finns små skillnader som på-verkar skapandet och körandet av enhetstester. Den mestiögonfallande konsekvensen av bristen på Objektorienteringi Clojure är att Dependency Injection måste ske på funk-tionsnivå, istället för klass- och objektsnivå. Man argumen-terar även för att den relativa omogenheten hos verktygsom används vid mjukvaruutveckling i Clojure kan ha ennegativ e�ekt på den Testdrivna Utvecklingsprocessen.

Glossary

CD Continuous Deployment. The process of often releasing a soft-ware product into its production environment, or at least anenvironment which is similar.

CI Continuous Integration. The process of often merging sourcecode between developers during development.

IDE Integrated Development Environment. A tool used in softwaredevelopment that often provides an editor, build automation,debuggers and other useful tools.

JVM Java Virtual Machine. A virtual machine that executes Javabytecode on many host platforms. Most Java bytecode run onthe JVM has been compiled from higher level programminglanguages, such as Java or Clojure.

OO Object-Oriented. A programming paradigm where valuesand functions are coupled as objects that are instances oftemplate-like classes.

REPL Read-Eval-Print Loop. An interactive prompt that takes pro-gramming expressions as input, evaluates them and presentsthe result to the user. Useful in exploring programming envi-ronments.

SOLID SOLID. A group of design principles in Object-Oriented pro-gramming languages. Short for Single responsibility, Open-closed, Liskov substitution, Interface segregation and Depen-dency inversion.

TDD Test-Driven Development. An iterative software developmentprinciple in which tests are written before any productioncode.

TF Test-First. The practice of writing software tests before theproduction code that it tests has been written.

TL Test-Last. The practice of writing software tests after theproduction code that it tests has been written.

XP Extreme Programming. An agile software developmentmethodology centered around developers that intends to im-prove quality by responding to change.

Contents

1 Introduction 11.1 Enter Clojure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.3 Problem statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.3.1 Scope and limitations . . . . . . . . . . . . . . . . . . . . . . 3

2 Background 52.1 The agile industry . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2.1.1 Classical product development . . . . . . . . . . . . . . . . . 52.1.2 Responding to change . . . . . . . . . . . . . . . . . . . . . . 52.1.3 Going agile . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62.1.4 Demands of the agile process . . . . . . . . . . . . . . . . . . 62.1.5 Sibling methods and principles . . . . . . . . . . . . . . . . . 7

2.2 Software testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.2.1 Classes of tests . . . . . . . . . . . . . . . . . . . . . . . . . . 72.2.2 Test automation . . . . . . . . . . . . . . . . . . . . . . . . . 8

2.3 Test-Driven Development . . . . . . . . . . . . . . . . . . . . . . . . 92.3.1 Core idea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.3.2 Benefits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.3.3 E�ects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.3.4 Test-First vs. Test-Last . . . . . . . . . . . . . . . . . . . . . 10

2.4 Continuous Integration . . . . . . . . . . . . . . . . . . . . . . . . . . 112.4.1 Pre-conditions . . . . . . . . . . . . . . . . . . . . . . . . . . 112.4.2 Automation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.4.3 Continuous Deployment . . . . . . . . . . . . . . . . . . . . . 122.4.4 Symbiosis with Test-Driven Development (TDD) . . . . . . . 12

2.5 Design patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122.5.1 Dependency injection . . . . . . . . . . . . . . . . . . . . . . 132.5.2 Dependency mocking . . . . . . . . . . . . . . . . . . . . . . . 142.5.3 Tests as documentation . . . . . . . . . . . . . . . . . . . . . 15

2.6 Clojure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162.6.1 Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162.6.2 Data types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2.6.3 Concurrency . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.6.4 Resource referencing . . . . . . . . . . . . . . . . . . . . . . . 182.6.5 Immutable values . . . . . . . . . . . . . . . . . . . . . . . . . 192.6.6 Platform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192.6.7 Special forms . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.6.8 Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

3 Method 213.1 Litterature study . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213.2 Programming activity . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4 Results 234.1 How Clojure di�ers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234.2 Testing macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254.3 Dependency injection in Clojure . . . . . . . . . . . . . . . . . . . . 25

4.3.1 Each dependency as an argument . . . . . . . . . . . . . . . . 264.3.2 Set of dependencies as a map . . . . . . . . . . . . . . . . . . 264.3.3 Set of dependencies as a protocol . . . . . . . . . . . . . . . . 274.3.4 Dependencies as context . . . . . . . . . . . . . . . . . . . . . 28

4.4 Untestable code in namespaces . . . . . . . . . . . . . . . . . . . . . 284.5 Mocking in Clojure . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4.5.1 Mocking injected functions . . . . . . . . . . . . . . . . . . . 294.5.2 Mocking non-injected functions . . . . . . . . . . . . . . . . . 304.5.3 Verifying function calls . . . . . . . . . . . . . . . . . . . . . . 30

4.6 Documenting Clojure . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.6.1 Expressiveness of tests in Clojure . . . . . . . . . . . . . . . . 32

5 Discussion 355.1 Lack of IDEs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365.2 Problems of debugging . . . . . . . . . . . . . . . . . . . . . . . . . . 365.3 Dynamic typing and productivity . . . . . . . . . . . . . . . . . . . . 385.4 Mocking made easier . . . . . . . . . . . . . . . . . . . . . . . . . . . 385.5 The REPL and automated tests . . . . . . . . . . . . . . . . . . . . . 39

6 Conclusions 41

Bibliography 43

Chapter 1

Introduction

The computer industry today is an exciting industry to be a part of. The develop-ment of new technology is happening at record speeds with new devices, interfacesand ways of interacting with the world quickly becoming essential parts of our everyday lives at a rate that can only be described as revolutionary.

Along with this increase in the amount of computer products present on themarket comes the increased need for talented developers who can quickly createstable and maintainable software that satiates the markets needs for quality prod-ucts. This pressure of shortening the time between a product’s conception to itsrelease becomes problematic when practicing the old waterfall-style of developmentthat tends to favor large scale, monolithic software within large timeframes andbudgets. Couple this with the improvements in technology that allows developmentteams to release their products with the click of a button and it’s no wonder thatagile methods have become so prevalent.

As more and more teams adopt agile methods in their every day work the pitfallsof the iterative development process become ever more apparent. When productscontinually grows and changes so does also the need for testing. It’s not longerpractically possible to postpone the product’s testing phase towards the end of itsdevelopment. As the rate of development changes, so must also the rate of itstesting. For the development process to be stable over time without a dramaticincrease in technical debt the testing of a product must be as continuous as itsincrease in functionality. The longevity of the development process has at timesbeen described as:

“Software development is a marathon, not a sprint” - Unknown

A development method that began to emerge around the same time as agile meth-ods were formulated was the method of TDD. This method embraces the testingpart of software development by not viewing tests as simply a means of verifyingprogram behavior but instead considering them to be the key driving force behindthe software’s development and design. TDD depends on the presumption thattests are quick to execute and the development community has as a response to

1

CHAPTER 1. INTRODUCTION

this found ways of reducing the time needed to run tests through the use of de-sign patterns. As Object-Oriented (OO) programming languages have been themost prominent languages in the industry during the last decade [6] these designpatterns have mainly been created to fit into the OO paradigm.

1.1 Enter Clojure

The deceleration in recent years of the number of transistors that can be fit into asingle processor together with the ever increasing need for processing large data setshas sparked the movement from single core to multicore processing. Harnessing thepower of these new generation multicore processors requires that developers pro-duce multithreaded applications, which opens up the possibility for several kindsof architectural problems of shared memory, thread locking etc. These problemsmust often be addressed manually when developing software in popular OO pro-gramming languages such as Java and the manual thread management can quicklybecome complex.

As a response to this problem of manual thread management came the pro-gramming language Clojure, which aims to eliminate most problems associatedwith multithreading while at the same time providing a light weight alternative tolarge enterprise languages such as Java.

Clojure is however a fundamentally di�erent language from these popular OOlanguages. This means that design and testing strategies that have been devel-oped in these languages aren’t always directly applicable when developing softwarein Clojure. Clojure also brings new concepts to the table that OO programmersprobably aren’t used to and thus have no experience of testing.

1.2 Motivation

The relatively young programming language Clojure is a response to problems ofsoftware development that are likely to continue to grow in the future. The factthat it di�ers in significant ways from more popular languages such as Java meansthat it can be di�cult to apply knowledge learned about testing in these languagesto Clojure. If one as a reasonably experienced Java developer wants to harness thepower of practicing TDD while leveraging the benefits of using Clojure one will thushave an advantage if one knows how to apply the knowledge of practicing TDD inJava when developing in Clojure.

The aim of this thesis is to show the di�erence one may encounter when transi-tioning from Java to Clojure and to show how old knowledge can be applied in thenew agile context of Clojure development.

2

1.3. PROBLEM STATEMENT

1.3 Problem statement• Which are the di�erences in available tools and language constructs between

Java and Clojure related to testing?

• How do these di�erences a�ect the creation and execution of unit tests?

• How do these di�erences a�ect the test first TDD process when developing inClojure?

1.3.1 Scope and limitationsThis thesis will only focus on unit tests and in particular on automated testing.Although there exists many popular OO languages today the only OO languagethat will be examined closely is Java 8.

Clojure is a hosted language that have implementations on several platforms.This thesis will only focus on the main Clojure implementation on the Java VirtualMachine (JVM).

3

Chapter 2

Background

2.1 The agile industryWhen developing software today the scopes of products often expand beyond whatis manageable by single individuals and multiple developers must thus work togetherin teams. As the complexity of products increases and the need for team members toorganize themselves within teams, as well as communicating with the world outsideof the team, there naturally arises a need for the use of product development methodsthat address these problems.

2.1.1 Classical product developmentBack in the olden days of the 1970’s, when the software development industryhad just started to bloom, the natural choice of product development method forsoftware projects was usually found by gazing towards similar but well establishedindustries. Proven development methods in industries such as manufacturing andconstruction were adopted and improved to suit the needs of the software industry.As projects succeeded and failed it was soon realized that the software developmentprocess could be divided into an analysis phase and a coding phase [7] - this generalidea is commonly referred to as the waterfall software development model.

Up until recently this linear style of software development has in one shape oranother been the industry norm and it has often proven to be quite e�ective in sup-porting the creation of successful products. One of its arguably greater shortcomingthough has always been that that it relies heavily on initial planning and as a resultof this responds poorly to unexpected change.

2.1.2 Responding to changeIt shouldn’t come as a surprise to anyone that humans are seldom capable of flaw-lessly planning ahead; it has been argued that it’s indeed impossible to create theperfect project plan [8]. Requirements may be incomplete, contradictory or changeand unforseen limitations and restrictions may be discovered while external pressure

5

CHAPTER 2. BACKGROUND

may be redirected. Response to these changes is hindered by the linear style of thewaterfall model, which has encouraged the search for a di�erent method of productdevelopment that is able to deal with critical changes. An answer came 2001 in theform of agile software development [9].

2.1.3 Going agileAgile methods are a family of product development methods that all have in com-mon that they adopt an iterative model rather than a linear one. The core ideabehind all agile methods is that products should be developed incrementally in shortintervals where requirements are revised and updated continuously. By doing thisthe intention is that changes in requirements are absorbed more easily in a projectwhen the development cycles are shorter than when they are longer, which increasesthe quality of the product and in turn reduces the risk associated with unexpectedchange.

The longer into the future that one has to plan the bigger is the probabilitythat unexpected change will occur. Agile methods’ approach to this problem is toreduce the time horizon for which planning takes place by working in incrementalsprints. Sprints are usually one-four week long periods that begin with the teamplanning the sprint ahead of them and often end with a demo of the product,which will be in a functioning state. There is usually a requirements document -commonly referred to as a backlog - present, but this is a dynamic document whererequirements can be added, removed, modified and reprioritized between sprintsand a set of requirements are usually picked to be worked on at the start of eachsprint.

When developing a product there must be some way of prioritizing functionalityin that will bring the most business value to the product. There must also besome way of determining if the implemented functionality reaches quality standards.Agile teams always employ a customer representative (sometimes referred to as theproduct owner) and she fulfills both of these roles. She doesn’t however perform anyactual planning of the work of individual team members as teams themselves areresponsible for deciding how the implementation will be made and how the workwill be divided within the sprint.

2.1.4 Demands of the agile processIn order for a team to be completely agile within a project there arises a set of de-mands that the team must work towards fulfilling which perhaps aren’t as commonin classical style of waterfall development.

Firstly, personal communication becomes more important. When the cycles areshort and changes occur often it can quickly become cumbersome to continuouslymaintain lots of documentation. The regular feedback with the project’s productowner leads to more time spent on the communication between her and the team.In software development projects the need for communication often implies that

6

2.2. SOFTWARE TESTING

the code produced by one team member must be comprehensible to other teammembers.

Teams are practically required to be small in size and self organizing when thereisn’t a large overall requirements document. As personal communication becomesmore key to success this also limits the team’s size for practical reasons. Require-ments are worked on one or a few at a time and only the team is assumed to knowbest how they should be implemented. The relative independence of the team alsoimplies that it has to be cross functional where all of the major competences neededto develop the product are present within the team.

The product has to be in a deliverable state after each development iteration.Since each sprint typically ends with a demonstration of the product this of courseimplies that it can’t be broken at the time of demonstrating and that all of itsfunctionality must be fully functional; otherwise there would be no way for theproduct owner to assure that the requirements have been met.

Testing must be performed often when iterations are short and the product isassumed to be in a continuously deliverable state. Both newly implemented as wellas old functionality must be frequently tested as the modification of existing codecan easily break what wasn’t broken in the past.

2.1.5 Sibling methods and principlesWhile there has emerged agile practices that tackles problems of a truly organiza-tional nature, such as Scrum [10] and Kanban [11], there has also emerged othermethods and principles that can be said to belong to the agile ecosystem but thatare tied more to software specific, technical issues. The main of these arguablybeing TDD [3] and Continuous Integration (CI) [4].

2.2 Software testingAn important part of product development is the task of quality assurance, whereit is assured that the product lives up to the expectations of its stakeholders. Theseexpectations may be divided into groups depending on the nature of the traits theytouch on, such as usability, reliability, performance etc.

In software development the technical quality assurance process is mainly donein the form of tests. A test is defined as a procedure where a set of actions areperformed on a product where the outcome of the actions are analysed. Actionsmay be performed by human hand or by automated systems and the outcomes arethe reactions of the product, its users, stakeholders or environment.

2.2.1 Classes of testsSoftware tests may di�er in level of detail, type of functionality under test andprerequisites necessary for their execution. It’s therefore of interest to separatetests into di�erent classes based on their characteristics.

7

CHAPTER 2. BACKGROUND

• Unit tests operate on a low level and are tightly coupled with source code.In the best of worlds one unit test always correlates to one minimal piece offunctionality without dependencies to other components, and when this is thecase they are quick to execute.

• Integration tests validate that software components work correctly when com-bined with each other. Only the interfaces between components are testedand it’s assumed that each component has been individually unit tested.

• System tests spans the entire system where most or all components have tobe set up and interconnected. This may increase the execution time of thetests since it demands more computing power and introduces latency betweencomponents - especially when connecting to external services.

• Performance tests measure the performance of critical parts of the system interms of execution time, memory usage, network latency etc.

• Usability tests are used to examine how users interact with the product andhow it impacts their overall experience of using the product.

• Acceptance tests determine that the product reaches quality standards set upby the product’s stakeholders.

2.2.2 Test automation

Manually executing tests is often a time consuming process, regardless of whichclass the tests belong to and it’s therefore of interest to consider automating testswherever possible. Acceptance and usability tests are generally interactive andrequire the involvement of people, which makes them di�cult - if not impossible -to automate. Unit, integration, system and performance tests are on the other handof a non-interactive nature, which means that they can be automated and that thetime spent on executing them can be significantly lowered from what it would be ifthey would be performed manually.

Long execution time of a product’s test suite forces developers to wait for thetests to finish, leading to a reduction in productivity as well as an increase in thelikelihood of developers not executing or adding new tests. This may in turn lead toa reduction in the overall quality of the product, which calls for the need to reducethe execution time of the tests. One way to accomplish this is to rely heavily on unittests rather than system tests. Unit tests are as a rule executed noticably fasterthan system tests because of their lack of coupling, but it’s easy for developersto introduce couplings in unit tests that go unnoticed. Such "unit tests" are infact system tests and are as a result slower to execute. Detecting and preventingcoupling in unit tests may thus reduce test execution time and positively impactthe overall quality of a product.

8

2.3. TEST-DRIVEN DEVELOPMENT

2.3 Test-Driven DevelopmentThe original purpose of software tests is that they should be used as a means tovalidate existing software and it’s thus natural to assume that they should be createdafter the actual software that they test has been implemented. With the adventof TDD [3] materialized the idea that tests can - in addition to verifying existingsoftware - also be used to shape the software and the process of developing it whenthey are created incrementally and iteratively before any actual code is created.

2.3.1 Core ideaThe TDD process promotes the idea that each test always tests a minimal piece offunctionality and that they are created one at a time. As tests are written beforeproduction code this leads to an iterative development process with the generalworkflow being:

1. Write a concise, minimal test for a new piece of functionality. This test willfail since the functionality isn’t implemented yet (if it passes one knows thatthe functionality either already exists or that the test isn’t designed correctly).

2. Implement the new functionality and run all tests, both the new and pre-existing ones. Repeat until all tests pass.

3. Clean up the code and ensure that all tests still pass, then return to step 1.

The focus on implementing minimal pieces of functionality one at a time means thatthe time it takes to complete each iteration generally ranges between 30 secondsto a few minutes. This process becomes cumbersome when tests take a long timeto execute and it’s therefore recommended for developers to focus on writing unittests instead of system tests.

2.3.2 BenefitsKent Beck argues in his book Extreme Programming Explained for the followingbenefits of practicing TDD [4]:

• Quick feedback. The programmer is instantly aware of whether new function-alities work as intended and if any already existing functionality has beenbroken.

• Task orientation. Writing small tests for each piece of functionality forces theprogrammer to decompose problems into small, manageable tasks.

• Focus. Having a single failing test to fix supports the programmer in focusingon a individual tasks.

• Progress. Unit tests become a reasonable metric for measuring the flow andprogress of the development process.

9

CHAPTER 2. BACKGROUND

• Stability. Letting tests drive the implementation of functionality the program-mer by design strives for total test coverage.

• Refactoring. Restructuring code becomes a less uncertain task when testsexist for all previously implemented functionality.

• Maintainability. Further work on the software is made easier when all imple-mented functionality can be tested.

• Testable software. It’s indeed possible to write untestable code. This kind ofcode can be a good place for bugs to remain undetected.

As an addition to this the fact that a software is accompanied by a set of teststhat cover all of its functionality means that developers have a plethora of exampleusages of the code and a living form of documentation over how individual piecesof code are expected to behave.

2.3.3 E�ectsSeveral extensive studies have been made with the intent of measuring the e�ec-tiveness of TDD, but the observed e�ects seem to vary between individual projectswhere the observed e�ects encompass productivity, defect density, maintainabilityand code coupling.

Most studies observe positive e�ects on maintainability, defect density and codecoupling but with varying productivity. Some observe a reduction in productivity[34], some observe an increase in productivity [16] while some finds no observablee�ects [15].

2.3.4 Test-First vs. Test-LastWhen discussing TDD it may be of interest to separate the cases when tests arewritten before or after the functionality they are intended to test into two categories:Test-First (TF) and Test-Last (TL). Strict TDD as first described calls for the useof TF while it’s arguably a common argument amongst individual developers thatone can do TDD using TL by switching the order of steps 1 and 2 in the TDD cycleand still achieve the same results as when using TF.

Empirical studies conducted on the practice of TDD and the di�erences betweenTF and TL have shown the di�erences between the two to be inconclusive in termsof productivity and quality. Some studies show that TF leads to greater code qualityin the form of coupling and greater test coverage [12], [34], while another showedno noticable di�erence in quality but a significant increace in productivity whenpracticing TF [16].

Productivity and software quality are di�cult metrics to measure. Since somebut not all projects seem to have noticable positive increase in these metrics and noreal negative e�ects have been discovered there exists some degree of justificationfor preferring TF over TL.

10

2.4. CONTINUOUS INTEGRATION

2.4 Continuous IntegrationWhen software development becomes a team e�ort where multiple developers arerequired to work with the same code there is the need for synchronizing the additionsand modifications between them. The iterative contribution of individual developersto a shared code base can be generalized as:

1. Check out code from a main branch.

2. Modify the code in a local working branch.

3. Merge ones work with the main branch.

4. Push the merge and make it the new main branch.

The longer a developer waits with merging the local and main branches in step 3the more extensive the merging process becomes; this is known as integration hell.Conflicting changes and broken code is usually discovered at the time of the mergingand the longer developers wait with merging local and main branches the greateris the amount of conflicts and broken code. There is therefore a motivation forperforming smaller merges often, as opposed to fewer but larger merges.

This practice of continuously integrating working branches between program-mers within a project is an idea first proposed as a part of Extreme Programming(XP) [4] and is known as Continuous Integration (CI). The small integration stepsprovide earlier warnings of conflicting changes and broken code, while increasingfeedback and greatly reducing the risk of near-release chaos. In the case that a bugis discovered the choice of reverting the code to an earlier bug-free state instead ofspending time and money debugging becomes a viable option.

2.4.1 Pre-conditionsThe frequent merging of di�erent branches practically necessitates the existenceof unit and integration tests. While it’s certainly possible to practice CI withoutautomated testing this will quickly become an uncertain and tiresome process, asdevelopers have little or no evidence of whether merges are successful or not if theydon’t manually perform tests after each merge (which takes time and e�ort).

Manually performing merges several times a day quickly becomes unmanageableand several developers often have the need to create local branches of the samesource files. There is thus a need for teams to use a central source control systemand tools assisting them in the merging of code. Luckily several of these systemsexist - the most widely adopted today arguably being git [1] and Subversion [2],both of which allow creating local branches and automatic branch merging.

2.4.2 AutomationA shared, automated build server can be used by teams practicing CI for a smallinitial investment in e�ort with a high payo� in long term automation. The build

11

CHAPTER 2. BACKGROUND

server is responsible for automatically running unit and integration tests, but moregeneral quality assurance procedures such as measuring and charting performanceover time or extracting and compiling documentation can also be performed auto-matically. The general idea here is that the e�ort spent constructing automatedprocesses will in the end be far outweighed by the benetits that CI brings with it.

2.4.3 Continuous DeploymentManually deploying software to testing, staging and release environments is often atime-consuming process. Teams are discouraged from deploying often as the deploy-ment process can take up to several days [5] and it isn’t unusual that deploymentsare done with months apart, regardless of whether the software is deployed to endusers or internally for developent purposes. The need for an automated deploymentprocess is thus present, but the problem is usually that each deployment requiresmanual quality assurance, which is labor intensive.

Having established the quality of the software many times a day using CI opensup to the possibility of automatically deploying the product to the actual environ-ment in which it will eventually be released, or at least an environment which issimilar. If the quality assurance process is completely automated deploying the soft-ware can in the best of worlds be done at the same rate as integration. This methodof short, continuous release cycles is commonly known as Continuous Deployment(CD).

Shorter release cycles implies that products reach end users at a more rapidrate and functionality with business value is usable at earlier times which in turnincreases overall profitability. Bug-fixes and patches can also be deployed morerapidly thus increasing the security of the software while reducing risk.

CD also strives to reduce the risk in the actual deployment of patches andimprovements, since the risk involved in deploying small sets of functionality isgenerally much lower than when deploying large functionality changes.

2.4.4 Symbiosis with TDDThe benefits of CI and CD relies heavily on the existence of small unit and integra-tion tests and great test coverage. Using these practices in conjunction with TDDsolves this dependency automatically and the practice of TDD thus opens up to theincreased benefits of CI and CD.

2.5 Design patternsSome problems within certain domains of software design can’t be solved at thelanguage level. Solutions to these problems may come in the form of best practicesand when these solutions are formalized they are known as software design patterns.Most design patterns in OO contexts are concerned with modularity and couplingand the way that they are applied varies between individual projects.

12

2.5. DESIGN PATTERNS

2.5.1 Dependency injectionAs software is modularized functionality is separated into components. A componentis loosely defined as an entity that serves some kind of distinguishable purpose,with some examples being classes, functions, external web services etc. When onecomponent’s functionality relies on the functionality of another component we saythat the former component is the client and the latter the service where the clientdepends on the service.

When setting up a dependency from a client to a service one has two choices,either to let the client itself create a link to the service to let an external coordinatingservice pass the service to the client. In the first scenario we have a strong couplingfrom the client to the service and it becomes problematic to have a client that canuse serveral services that implement the same API. In the latter scenario we can atthe cost of a little extra coordination overhead create clients that only depend onthe API of a service and not actual implementations of the API.

This method of loose coupling between components where clients only knowof its service’s API and gets instances of the services passed to them by externalcoordinating components is known as dependency injection design pattern (the Din SOLID (SOLID) [14]). Martin Fowler - the person who first coined the term -identified three ways of implementing the design pattern in OO languages [13]:

• Constructor injection: injecting dependencies into constructors when instan-tiating objects.

• Setter injection: providing setter methods where objects’ dependencies maybe provided.

• Interface injection: basically the same as setter injection, but the setter meth-ods are declared in interfaces implemented by client objects.

An example of how a client-service relationship might look without using depen-dency injection consider the following Java class that is used for logging and thathas a strong coupling to a database:

public class Logger {private DatabaseExample database;

public Logger() {this.database = new DatabaseExample();

}

public void log(String message) {database.write(message);

}}

13

CHAPTER 2. BACKGROUND

Now assume that we want to create several instances of the logger class where eachinstance writes to a di�erent kind of database. This simply isn’t possible in theabove example as each instantiation of the Logger class always creates a connectionto the same database class. Consider the following class that resolves this issuethrough constructor injection:

public class Logger {private DatabaseExample database;

public Logger(DatabaseExample database) {this.database = database;

}

public void log(String message) {database.write(message);

}}

This loosely coupled example shows how one may use dependency injection wherethe Logger class is the client that depends on the DatabaseExample service. It’snow possible to inject any implementation of the service into the client and thefunctionality of the client is completely separated from any implementation of theservice.

Implementing the dependency injection design pattern in a system means thatthe configuration and creation of links between components can be moved out ofindividual components into a single coordinating one, thereby reducing the com-plexity of the system. It also makes it easy to replace one implementation of aservice used by a client with another one, which in turn enables new ways of test-ing clients without relying on implementations of services through the use of mockobjects.

2.5.2 Dependency mockingAlthough the architectural separation of clients and services through the use of de-pendency injection easens the process of modularizing software the testing of clientsstill require that there exists implementations of the services - since the functional-ity of clients depends on the functionality of services. But when we introduce botha client and the services it depends on in the client’s tests we’re implicitly testingthe functionality of the service as well. In other words the tests that are supposedto be a client’s unit tests are in fact partial or complete system tests, which may insome cases be problematic:

• Service functionality is slow, making the execution time of unit tests longer.

• Functionality provided by the services are non-deterministic, leading to testswith unpredictible results.

14

2.5. DESIGN PATTERNS

• State in the services is di�cult to set up or tear down.

• The services don’t exist or can’t be used yet.

In the late 90’s a group of developers and system architects began researching waysof eliminating these kinds of problems in OO systems [20] and the result was a testdesign pattern called object mocking [19].

A mocked object is an instance of an object that behaves as an instance of acertain class but with functionality that is tailored for special use cases withoutrelying on an actual implementation of the class. They are mainly used in unittesting of clients, where the values and methods of the services a client is dependenton are mocked and injected into the client without introducing any actual couplingbetween existing components.

Although there has been some controversy regarding the terminology of mockedobjects in term of the functionality they provide [23] there will be no such distinc-tions made in this thesis and all all sorts of mocked objects will simply be referredto as mocked objects.

Unit testing components with dependencies is practically impossible without themocking of objects. Since TDD relies heavily on unit testing, object mocking is thusa crucial part of the Test-Driven Development process.

2.5.3 Tests as documentationAs multiple people collaborate in developing software they need some sort of methodcommunicating about the behavior of the software, such as descriptions of the be-havior of functions, example code illustrating how the software is used or the overallsystem design. This need for communication exists internally between developersand may also exist externally to the users of the software’s API. The behavior of thesoftware can of course be communicated verbally between people, but this easilybecomes uncumbersome as developers forget how the software is implemented orwhen the amount of required communication increases. It has therefore tradition-ally been custom to write down the expected behavior of programs into documentsthat are shared between those who might need them.

This traditional form of communicating through documents has a weakness inthe sense that the documentation is completely separate from the actual code -whenever the code is updated developers must also rewrite parts of the documen-tation, a task that is easily overlooked or forgotten and as a consequence the docu-mentation may be incomplete and/or contain errors.

One way of facing this problem of inconsistency between code and documenta-tion is to allow the documentation to be an actual part of the code that doesn’t haveto be maintained separately by letting tests serve as documentation. When eachtest tests a single piece of functionality and is constructed in such a way that us hu-mans are able to understand which functionality is being tested, the test fulfills therole that the separate documentation was otherwise supposed to serve since we’re

15

CHAPTER 2. BACKGROUND

able to extract information from the test about what functionality is implementedand examples of how it’s supposed to be used.

The view point of keeping unit tests as documentation has several advantageswhen used in conjunction with TDD. Firstly the tests are always up to date andconsistent with the functionality described as they are run continuously duringdevelopment when practicing TDD. One therefore never have to worry that thebehavior described is inconsistent with the actual behavior of the software. Similarlythere’s also the positive e�ect of having no undocumented behavior since TDDstrives for total test coverage of all implemented functionality. Another implicite�ect is the fact that developers won’t have to commit extra time to writing separatedocumentation as the tests are already there.

This method does however require that the developers put e�ort into makingthe tests expressive, since other people (and at a later point in time they themselvestoo) are supposed to be able to quickly understand which behaviors are being tested.A common way of achieving this goal is to make each unit test verify one certainbehavior and name it in a descriptive way; an example of how this might look inJava using JUnit is:

@Testpublic void twoPlusThreeEqualsFive() {

assertEquals(5, 2 + 3);}

Apart from naming the tests most languages support some way of annotating themwith actual text phrases that are supposed to be a description of the tests. Byassembling all of the tests into a list one should this way be able to get a fulldescription of all behaviors that can be expected from a piece of software andexamining into individual tests provides usage examples.

2.6 ClojureClojure is a relatively young programming language with version 1.0 released in2009 [32]. It is in the words of its creator Rich Hickey [33] a language based aroundthe core ideas of having a programming language that is a functional Lisp dialectwith an emphasis on immutability and concurrency, while being designed to runon host platforms with a great deal of transparency to the underlying platform’slibraries and capabilities.

2.6.1 SyntaxClojure belongs to the Lisp family of languages - a group of languages that areall based on a computational model and syntax first described in the late 50’s byJohn McCarthy [29]. As with other Lisp dialects all code and data are syntacticallyand computationally interchangeable in Clojure, where expressions are written as

16

2.6. CLOJURE

paranthesized lists. The first item in a list is interpreted as a function with thefollowing items being arguments to the function:

(f arg1 arg2 arg3)

Function calls may be nested to create more complex computations:

(f arg1 (g arg3 arg4) arg2)

Lists, the most important data type in Clojure, are created with function calls:

(list 1 2 3) => (1 2 3)

but in practice lists are often created using syntactic sugar for readibility:

’(1 2 3) => (1 2 3)

Commas and white space are interchangable:

’(1,2,3) => (1 2 3)

2.6.2 Data typesClojure implements the primitive data types common to most other programminglanguages, such as integers, floats, strings etc. There are four collection data typescommonly created with syntactic sugar:

Singly linked lists, as described above:

’(1 2 3)

Directly indexable vectors:

[1 2 3]

Sets that contain unique values:

#{1 2 3}

and key-value maps:

#{:key1 1 :key2 2 :key3 3}

Symbols beginning with a colon are interpreted as atomic keywords. The four maincollection data types share some properties in the sense that they are sequences ofvalues. They are thus commonly referred to as seqs when their sequence propertiesare applicable and seqs can be lazy. Since Clojure is a dynamically typed languagevalues of di�erent types are thus interchangable in code and their type is checkedat runtime rather than compilation. Collections are allowed to contain values ofdi�erent types.

17

CHAPTER 2. BACKGROUND

2.6.3 ConcurrencyClojure is a language with the concept of concurrency built into its core [25]. Themain creator of Clojure, Rich Hickey, has in his work with Clojure reduced problemof concurrency into the identity-value conflict - the inability to separate referencesto values from the values themselves [26]. A reference shared by multiple executingthreads may be altered to refer from one value to another by one of the threads andthis alteration will be visible by the other threads. Similarly the values that arebeing referred to are stored in physical memory locations and may also be altered byother threads during their execution, leading to classical race conditions when mul-tiple threads reads and writes to the same memory locations simultaneously. Whendealing with concurrency one must thus address the problems of the coordinationof references and the mutability of values. Clojure attempts to solve the formerproblem by supplying and forcing the use of several methods of thread-safe resourcemanagement in which references to shared resources are handled and coordinatedin safe ways between threads that doesn’t lead to race conditions and the latter byimplementing a memory model of immutable values where values once never changeafter they have been set.

2.6.4 Resource referencingAs with most (if not all) programming languages Clojure has ways of binding sym-bols to values where a symbol in practice is the textual representation of a referenceto a value which a programmer may use in the source code of an application. Clojuresupplies four ways of doing this with Vars, Refs, Atoms and Agents.

In Clojure symbols are bound to values with Vars - bindings that are either static(which is the default behaviour) or dynamic and bound on a per-thread basis. If aVar is static it can’t be rebound and is thus thread safe, while if dynamic the Varis thread local and thread safe since it can’t be accessed by other threads. The defspecial form in the following example binds the symbol my-var to the value "sometext":

(def my-var "some text")

The value referred to by a Var can either be an immutable value, or an immutablecontainer of a mutable reference to another immutable value that provides threadsafe ways of managing wrapped reference. Clojure provides three ways performingthis mutable reference management with Refs, Atoms and Agents that all implementa transactional memory model and provide di�erent functionalities:

• Refs provide coordinated synchronous access to multiple resources.

• Atoms provide synchronous access to single resources.

• Agents provide asynchronous access to single resources.

18

2.6. CLOJURE

The common use cases for Atoms are when multiple threads needs access to and theability to modify a single resource without the occurrence of race conditions, whileRefs are similarly used for grouping together several resource modifications into asingle atomic transaction. Agents are on the other hand commonly used when themodification of a resource is asynchronous and the thread accessing the resourcedoesn’t have to wait for the modification to execute.

2.6.5 Immutable valuesIn Clojure a value may never change (mutate) once it has been created and storedin memory, which means that Clojure’s values are immutable. The only way ofchanging the value that a reference points to is thus to make the reference to pointto a new value stored in a di�erent memory location. This di�ers from how memoryis clasically treated in OO languages, where the reference may continue to point atthe same memory location while the actual value stored at that memory locationchanges.

The Clojure way of maintaining the immutable state of values ensures that onecan always be sure that the value read by a thread will always stay the same,regardless of mutations performed by other threads. In practice this also meansthat one doesn’t have to worry about a value changing after it has been passed toa function and the process of creating thread safe, concurrent programs is madesimpler.

Creating new values such as eg. integers is cheap, but when dealing with largedata structures - such as lists containing thousands of items - making a new copyof the data each time the list is modified would of course be an expensive oper-ation. Clojure thus implements collections as self referencing trees where makingnew copies is cheap both in memory consumption and processing time [27]. Thoughthis is a rather complex structure when compared to eg. singly linked lists, the im-plementation is hidden away behind layers of abstractions and programmers mayuse the collection data structures without any knowledge of their actual implemen-tation.

2.6.6 PlatformClojure is hosted on the JVM platform and shares its garbage collection, threadsetc. Clojure source code is interpreted and compiled to JVM bytecode either aheadof time or at runtime and Clojure supports the ability to natively implement Javainterfaces and classes. It’s also possible in Clojure to interact with existing Javacode and developers thus have easy access to already existing Java code.

There has been some attempts made to port the Clojure language to otherplatforms as well, some notable examples being ClojureScript [30] that compilesClojure code to JavaScript and ClojureCLR [31], a Clojure implementation on the.NET platform. These spin o�s aren’t however within the scope of this thesis and- although interesting in their own right - won’t be discussed in detail.

19

CHAPTER 2. BACKGROUND

2.6.7 Special formsMost of the core functions in Clojure are written in actual Clojure code, but in orderfor the high level Clojure code to be able to communicate with the machine theremust exist some kind of link between Clojure code and the underlying platform.These links come in the form of functions known as special forms and they areimplemented in Java code. Among them are eg. the if-then-else special form andthe def special form. Although they are treated di�erently by the Clojure compilerthan other Clojure code, there are no practical di�erences in how they are used bythe programmer.

2.6.8 MacrosA macro is a tool for altering a data structure into code which can then be in-terpreted by the Clojure compiler. Macros can be defined and expanded by pro-grammers, thus allowing the core language to be extended with new syntacticalconstructs. This is made possible by the fact that Clojure is homoiconic, ie. thestructure of its source code is the same as the structure of its abstract syntax tree.An example of a Clojure macro is the "thread-first" macro:

->

which in the fictional example:

(-> "some text" read parse evaluate print)

is evaluated as:

(print (evaluate (parse (read "abc"))))

after it has been expanded by the Clojure compiler.

20

Chapter 3

Method

The methods used in this thesis consists of a study and analysis of literature, aswell as programming activities. The work began with an extensive literature studyalong with an analysis of how OO practices could be applied in Clojure, specificallytaking into consideration of the di�erences between Java and Clojure. To get a firsthand understanding of how the TDD process di�ers between development in Javaversus Clojure and what consequences these di�erences may lead to there were twoidentical programs created in both languages.

3.1 Litterature studyClojure is a relatively young language with an active community of bloggers andenthusiasts. As a result of this the available literature on development practices andexperiences in Clojure are mostly anectodal rather than academical. The problemof trying to analyze this litterature thus consists of trying to sift out general andobjective facts.

Lisp dialects have existed for many decades and some research has been pub-lished on the lingual benefits of using Lisp syntax and code structure. Since Clojureis a dialect of Lisp this research has been taken into consideration when applicablein Clojure.

There exists an abundance of research done in the field of TDD but these havelargely been written in OO contexts. These served as a platform and referencepoint for justificating conclusions on how the TDD process may be a�ected by thedi�erences Java and Clojure.

3.2 Programming activityIn order to get an understanding of how the creation of tests, their impact onsoftware design patterns, the available tooling and the overall TDD process di�ersbetween the languages there were two applications created from scratch - one in

21

CHAPTER 3. METHOD

Clojure and one in Java - that have identical external interfaces and thus can beconsidered to provide the same functionality.

For the programming activity to be relevant it was important that the applica-tions touched on the more complex parts of unit testing and the criteria put on theapplications were that they should:

• Have a minimal reliance on external language-dependent libraries

• Implement a high level of modularity

• Introduce several mockable services

• Maximize the amount of unit-testable functionality

There were however no demands made on the performance of the applications, otherthan that they were reasonably similar and e�ective.

The application that was chosen to be developed in both languages was animplementation of the game Checkers [18] that has a text only interface and is runby automated AI’s. It’s assumed that the time it took to develop two versions ofthe game was within the scope of the time available for this thesis.

These implementations were chosen for several reasons. First of all the minimaltext based UI and the fact that AI’s play the game leads to little user interaction.Testing user interaction is thus limited and more focus can be put into testing gamefunctionality with unit tests. Secondly there are several variants of Checkers withdi�erent board sizes and rules, which opens up for the possibility of modularity,inversion of control and dependency mocking. Lastly there’s no need for externallanguage-dependent libraries, which means that the implementations can be com-pletely written in native code.

22

Chapter 4

Results

4.1 How Clojure di�ersThe first step in determining how the di�erences between Java and Clojure a�ectsunit testing and the TDD process is to examine in which fundamental ways the twolanguages di�er. This thesis identifies the following core features found in Clojureand not in Java:

• Dynamic execution

• Dynamic typing

• Lisp syntax

• Macros

• Namespaces

• Immutable values

• Transactional memory

• First-class functions

• No object-orientation

• Creating documentation

• Available tooling - such as editors, frameworks, and debuggers

By closely examining this list of features it’s possible to observe and deduce howthey may a�ect unit testing and the TDD process.

23

CHAPTER 4. RESULTS

Observation 1 - Macros

The arguably most striking di�erence between the two languages is the fact thatmacros are a core feature of Clojure but not Java. There’s no practical way ofcreating self modifying code in Java and as a consequence of this there’s also noexperience in how such code should be tested. Section 4.2 will explain and motivateone way of testing macros in Clojure.

Observation 2 - Dependency injection

The idea of dependency injection is key in TDD since the injecting of dependenciesis a pre-requisite in the process of dependency mocking, which in turn is practicallya necessity if one wants to be able to rely on unit testing of coupled components.Dependency injection has however primarily been described in an OO context wheredependencies are injected into classes. Due to Clojure’s lack of OO one thus have tofind other ways of injecting dependencies that don’t rely on the existence of classes.Section 4.3 will explain and motivate several ways of injecting dependencies intocomponents in Clojure.

Observation 3 - Dependency mocking

A dependency in Java generally comes in the form of a class and mocked objects arethus ad hoc instances of classes. Since Clojure is classless the dependencies have totake on other forms of data structures than classes and section 4.5 will explain thisfurther. It will also show how one is able to inline mock objects directly in testswithout the need for any frameworks, due to the dynamic nature of Clojure.

Observation 4 - Documentation

Documentation is an important part of all software projects. In Java the JavaDoctool is industry standard for coupling documentation and code together but Clojuredoesn’t have a similar tool. One must thus find another way of documenting code.The principle of keeping unit tests as documentation might also be di�erent inClojure when compared to Java due to the di�erences in syntax and test tooling.Section 4.6 will explain ways to tackle these two problems in Clojure.

Observation 5 - Loading namespaces

The way namespaces are loaded in Clojure means that any runnable code that isn’ta definition is run when the namespace is loaded, thus creating an opportunity forundestable code to creep in. Section 4.4 gives a short explanation of how this worksand how it should be avoided so that code can be clean and unit testable.

24

4.2. TESTING MACROS

4.2 Testing macrosAlthough the prospect of testing self modifying code might seem daunting at first,it’s actually a trivial task to do so in Clojure. Remember that Clojure code isstructured as lists and that a macro is basically a function that restructures a pieceof code. Testing a macro is thus the simple task of validating that the macrocorrectly expands input lists into output lists.

As an example consider a macro that takes an expression written in infix notation(which of course Clojure can’t interpret by default) and translates it into a regularClojure prefix notation expression:

(defmacro infix [expr](concat (list (second expr) (first expr))

(drop 2 expr)))

This macro is expected to make the transformation:

(infix (a f b)) => (f a b)

To test that the macro works as intended we simply expand the macro on a sampleexpression and compare it to the expected result:

(= ’(+ 1 3) (macroexpand ’(infix (1 + 3))))

It should be obvious that testing macros is just like testing regular list transfor-mations, which shouldn’t be an unfamiliar task to reasonably experienced Javaprogrammers. The above above example is of course rather simple and macros maybe structured in more complex ways, but the principle of list transformation stillstays the same.

4.3 Dependency injection in ClojureThe basic component in Java and other OO languages is the object, which by designis always an instance of a class (one could argue that the primitive data types arealso basic components in Java, but because these types are so simple they won’tbe considered as components for these purposes). Since dependency injection is theprinciple of linking together components and Clojure doesn’t implement any formof classes the basic component in Clojure must be defined as something other thana class if one wants to practice dependency injection in Clojure.

The basic component in Clojure will instead be defined as either a function, adata structure or a value. Values and data structures are objects that only containvalues without performing any computations and it does therefore not make senseto pass other components into them as dependencies. The process of injectingdependencies in Clojure is thus the process of dynamically providing functions accessto other functions, values and/or data structures.

25

CHAPTER 4. RESULTS

Bringing a set of dependencies into the context in which a function executescan be done in two ways - either by passing the dependencies as arguments to thefunction or by binding the dependencies in the context in which a function is called.

Let’s begin by taking a look at how dependencies might be passed as argumentsto functions. This can be done in two ways, by passing each dependency as a singleargument or passing sets of dependencies as single arguments.

4.3.1 Each dependency as an argumentThis is arguably the most trivial design for injecting dependencies. Consider theimplementation of two service functions used for saving and fetching data from somekind of storage:

(defn save [coll id](... implementation of function ...))

(defn fetch [coll id timeout](... implementation of function ...))

A client function that depends on these two operations might look something like:

(defn my-fun [save fetch coll id timeout](... call (save coll id) ...)(... call (fetch coll id timeout) ...))

The service functions might with this simple design be mocked when testing orreplaced with other implementations with the same API. Calling the client functionwith implementations of its dependencies is trivial:

(my-fun save fetch coll id timeout)

A problem with this design is that as the number of dependencies increase for theclient function it may become an uncumbersome way to redirect dependencies. Codebecomes more cluttered and thus less readable, as well as making refactoring moretroublesome.

4.3.2 Set of dependencies as a mapWith this design all dependencies for a function are contained within a single datastructure, with the intention of leaving the code less cluttered, and there are afew practical ways of doing this. The arguably easiest way of bundling togetherobjects and functions into a single dependency object is by using maps. Objectsand functions can be trivially inserted into and retrieved from a map using keys andthere is of course no need to use every value in the dependency map, meaning thatthe same dependency map can be passed to many di�erent functions without anyextra overhead. Using the above example the client function would be redefined as:

26

4.3. DEPENDENCY INJECTION IN CLOJURE

(defn my-fun [storage coll id timeout](... call ((:save storage) coll id)(... call ((:fetch storage) coll id timeout))

The already defined service functions are put in a map:

(def storage {:save save:fetch fetch})

And the client function would be called with the map as an argument:

(my-fun storage coll id timeout)

4.3.3 Set of dependencies as a protocolWhen there exists multiple unique and interchangeable implementations of a depen-dency with the same externally visible interface it’s possible for the sake of clarityand extensibility to use protocols. A Clojure protocol is a declaration of a set offunction signatures that might be implemented in multiple ways, which makes pro-tocols similar to Java’s interfaces. Protocols may be defined with the defprotocolspecial form:

(defprotocol Storage(save [this coll id])(fetch [this coll id timeout]))

And the protocol may have multiple service implementations that are created withthe reify macro:

(def db-storage(reify Storage

(save [this coll id]( ... implementation of function ...))

(fetch [this coll id timeout]( ... implementation of function ...))))

(def in-memory-storage(reify Storage

(save [this coll id]( ... implementation of function ...))

(fetch [this coll id timeout]( ... implementation of function ...))))

These service implementations may later be passed to the client function regardlessof which implementation is used:

27

CHAPTER 4. RESULTS

(defn my-fun [storage coll id timeout](... call (save storage coll id) ...)(... call (fetch storage coll id timeout) ...))

4.3.4 Dependencies as contextThe main idea behind this design is that a client function gains access to its depen-dencies through the external scope in which it’s executed, rather than as arguments.Both methods will in practice produce the same results, as arguments can be seenas belonging to the the execution scope of a function, but the resulting code di�ers.The above examples would with this method looks like:

(defn my-fun [coll id timeout](... call (save coll id))(... call (fetch coll id timeout))

The service functions must then be bound in the context which calls the function:

(with-redefs [save (... set up dependency ...)fetch (... set up dependency ...)]

(my-fun coll id timeout))

Though it’s entirely possible to use this approach e�ectively it has two importantimplications. Firstly the compiler will complain upon loading the client functionthat the service functions aren’t defined within the client function’s context. Theservice functions must thus be bound to some default value for the code to be ableto compile. Secondly when dependencies are purely contextual two calls to thesame function and with the exact same arguments may produce di�ering resultsdepending on where they are placed in the code, as opposed to calls to functionswhose dependencies are all arguments.

The fact that the function creates di�erent results depending on the context inwhich it’s executed means that it’s impure, while the previous examples of clientfunctions that aren’t context dependent are pure (assuming that the service func-tions are pure as well). Pure functions are generally easier for programmers to wraptheir heads around and reason about since one doesn’t have to expand one’s focusoutside of the internal context of a function. It’s therefore recommended to optfor the approach of passing dependencies as function arguments rather than havingdependencies as bound by context.

4.4 Untestable code in namespacesCode placed in OO constructors that performs logic is a breeding ground for untestablecode. When a dependency is set up within an objects constructor rather than be-ing injected as a dependency it’s practically impossible to mock or replace thatdependency since that code is run at the creation of each instance of the class.

28

4.5. MOCKING IN CLOJURE

In Clojure there are no classes and therefore no constructors either, but a similarproblem still exist with namespaces. By design Clojure evaluates all expressionspresent within a namespace as it is loaded at runtime. This means that any looselogic within a namespace that is something other than a pure definition will be runas the namespace is loaded. As an example of this consider the following namespace:

(ns example-namespace)

(def example-var ...)

(print "Hello")

(defn example-function ...)

Loading this namespace will apart from bringing the var and function into scopealso print the string "Hello", i.e. the loading of the namespace is not free from sidee�ects. Although this example has a rather harmless side e�ect it’s possible thatthere may be code run that leads to more complex behaviors. These behaviors arein the best of cases hard to test and in the worst untestable, where dependencies areimpossible to mock. For the sake of testability one should thus avoid including anylogic in namespaces that will be evaluated when the namespace is loaded if possible.

4.5 Mocking in ClojureMocking anything but the most simple objects in Java practically necessitates theuse of external frameworks that support the creation and inspection of mock objects.While external mocking frameworks are available in Clojure, such as Midje [24], it’sboth technically and practically possible to create mocked functions and objects innative Clojure without the use of external frameworks. How this may be done willbe shown in a few general scenarios.

4.5.1 Mocking injected functionsConsider the following example client function add-and-square, whose functional-ity depends on the service function add-fn injected as a function argument:

(defn add-and-square [add-fn x y](let [z (add-fn x y)]

(* z z)))

If one wants to test this client function without depending on an actual implemen-tation of the service function add-fn one will have to set up a mock function thatcan be used as an argument in a unit test:

(deftest test-add-and-square

29

CHAPTER 4. RESULTS

(let [add-fn (fn [x y] 5)](is (= 25 (add-and-square add-fn 2 3)))))

In this example the mocked function add-fn always returns 5, regardless of whicharguments it’s called with. Instead consider that one wants to make a mockedfunction that returns di�erent values depending on which arguments it’s calledwith. One arguably simple way of doing this is by pattern matching and mappingthe expected input arguments to custom output values:

(deftest test-add-and-square(let [add-fn (fn [x y]

(case [x y][2 3] 5[3 5] 8))]

(is (= 25 (add-and-square add-fn 2 3)))(is (= 64 (add-and-square add-fn 3 5)))))

4.5.2 Mocking non-injected functionsImagine that one didn’t - for some reason - opt to go the dependency injectionroute in the first example above and instead introduced a hard coupling from theadd-and-square function to the add-fn function:

(defn add-and-square [x y](let [z (add-fn x y)]

(* z z)))

If we want to create a true unit test for this client function we must be able to dothis without depending on an actual implementation of add-fn. This is possible byrebinding the symbol bound to add-fn in the context in which add-and-square isexecuted:

(deftest test-add-and-square(with-redefs [add-fn (fn [x y] 5)]

(is (= 25 (add-and-square 2 3)))))

4.5.3 Verifying function callsIt’s not unusual when testing client functions that it’s only important to know if aservice function has been called or not, without caring about which values the servicefunction returns. As an example consider the client function parse-if-active thattakes an event and a service function parse-fn as input arguments and makes acall to the service function if the status of the event is active, without caring aboutany return values:

(defn parse-if-active [parse-fn event]

30

4.6. DOCUMENTING CLOJURE

(if (= :active (:status event))(parse-fn event)))

Verifying that a function has been called a number of times in Clojure - with theability to check with which arguments the function was called - is a bit trickier thanjust mocking function behavior without the use of frameworks, but its’ possible todo this in a few rows of native Clojure code. One way of doing this is:

(deftest test-parse-if-active(let [event {:status :active, :value :some-value}

args (atom [])parse-fn (fn [event] (swap! args conj event))]

(parse-if-active parse-fn event)(= @args [event])))

This approach creates a mutable vector args that will contain the arguments thatthe mocked service function parse-fn will be called with. parse-fn is implementedin such a way that each time that it’s called it will append its input arguments to theargs vector. Checking if the parse-fn function was called with certain argumentsis then just a matter of inspecting the args vector. Counting the number of timesthe parse-fn function was called is equal to counting the number of elements inargs and since the each insertion into the vector is done at its tail it’s possibleto check in which order multiple calls to the function was made by doing indexcomparisons.

4.6 Documenting ClojureThe industry standard for documenting Java source code is arguably through JavaDoc[28], a tool bundled as a part of the Java language. With JavaDoc developers man-ually maintain the documentation which becomes a part of the source code. Thisdocumentation is however independent of the software in the sense that it’s puretext that can be extracted and parsed into HTML out of the source files, butisn’t present in the compiled software at runtime. An example of how a section ofJavaDoc documentation might look in source code is:

/*** The mathematical operation of exponentiation.** @param b The base of the operation.* @param n The exponent of the operation.* @return b to the power of n (b^n).*/

public int exp(int b, int n) {... implementation of method ...

}

31

CHAPTER 4. RESULTS

Clojure supports a di�erent approach to documenting code where documentationaltext is stored as metadata for Vars and special forms. When the documentationis stored as metadata it isn’t only present in the source files but is also availableat runtime, which can be useful when interacting with applications through theRead-Eval-Print Loop (REPL). An example of how documentation is created asmetadata in Clojure is:

(defn exp"The mathematical operation of exponentiation.Returns the value of b to the power of n (b^n) whereb is the base and n is the exponent of the operation."([b n]

(... implementation of function ...)))

Both methods requires that developers maintain the documentation manually andthere’s thus little di�erence between them in practice.

4.6.1 Expressiveness of tests in ClojureFor tests to be expressive enough to be able to be considered as an alternative wayof documenting software the testing environment must satisfy a few conditions. Itmust within the testing environment be possible for developers to:

• create tests that test one piece of functionality,

• follow the body of a test and understand what is being tested, and

• give meaningful and representative descriptions of each individual test.

As will be shown it’s in fact true that Clojure’s native testing environment satisfiesall of these conditions.

In order to test an individual piece of functionality within a single test one mustto be able to separate the functionality under test from its dependencies. As we havealready shown it’s entirely possible to mock dependencies in Clojure and assumingthat the functionality under test has been implemented in a modular fashion it’stherefore possible to create unit tests that test an individual piece of functionality.

It shouldn’t be a problem for a developer to look at the body of a test andunderstand what is being tested as long as the test is cleanly written and thedeveloper has a decent understanding of the language.

Giving a meaningful description of a test can in practice be done in two ways- either by linking the test with a descriptive text string or by giving the test aname that is descriptive of the functionality that is being tested. Since it’s possibleto name functions in clojure using regular letters and many of the other ASCII-characters the naming of functions can be done in an arguably descriptive way:

(deftest two-plus-three-is-five(is (= 5 (+ 2 3))))

32

4.6. DOCUMENTING CLOJURE

If one for some reason would rather describe a test in the form of free text this issupported by Clojure’s native testing framework by associating the assertion witha string:

(deftest a-test(is (= 5 (+ 2 3))) "two plus three should be equal to five")

or by wrapping multiple tests in several descriptions:

(deftest several-tests(testing "mathematical operations"

(testing "addition"(is (= 5 (+ 2 3)))(is (= 8 (+ 6 2))))

(testing "multiplication"(is (= 8 (* 4 2)))(is (= 0 (* 0 7))))))

Regardless of which way one chooses to describe one’s tests it should be obviousthat it’s indeed possible to create just as expressive and descriptive tests in Clojureas one may in Java.

33

Chapter 5

Discussion

This thesis is based around the hypothesis that the di�erences between Clojure andJava would in turn lead to great di�erences in how unit testing is done and TDD ispracticed in both languages respectively. As this hypothesis was analyzed closer itbecame more and more apparent that the results expected to be found weren’t asdramatic as first assumed.

This is likely due to the fact that even though the languages di�er much inappearance and implementation they still operate within the same domain. Beinggeneral purpose languages means that everything that is possible to do in Clojureis also possible in Java. Although the manner in which developers may chooseto create their implementations and thus their unit tests may di�er, the fact stillremains that unit tests are simply small, modular tests that test certain pieces offunctionality. As long as it’s possible to separate dependencies it’s also possible tocreate idiomatic unit tests. Thus the concern of trying to find the di�erences in howunit testing is a�ected by the lingual di�erences is mainly the problem of trying tocreate modular code without couplings.

One greatly impactful di�erence between the two languages unrelated to depen-dency separation is the tools available to developers that may be used for develop-ment in each language. Both languages have active communities that continuouslyfind new problems and invent tools that attempts to solve them and one can’t for-get that each individual developer has the freedom to choose her own developmentenvironment. This makes it di�cult to make any sort of analysis on tooling di�er-ences and any attempt to do so would merely be a snapshot of the current situationthat would quickly become outdated. It’s however safe to say that the fact thatClojure has been around for a significantly shorter time than Java means that thereare naturally fewer and less mature tools for Clojure developers to choose from.Perhaps this will change in the future, though.

35

CHAPTER 5. DISCUSSION

5.1 Lack of IDEs

There exists a few all encompassing Integrated Development Environments (IDEs)for Java development, like Eclipse [35] and IntelliJ [36], in which developers basicallyhave everything they need, from package management and automatic builds to codecompletion and refactoring tools. Clojure does currently not enjoy this kind ofluxury with end-all be-all IDEs and one must thus instead use an ecosystem ofspecialized tools.

While there exists Clojure plugins to major IDEs like Eclipse and IntelliJ theseare lacking in certain features and still relies on external Clojure specific tools, theprimary one being Leiningen [38] for compilation, running tests and executing ap-plications. Other alternatives for Clojure development is using highly customizabletext editors like Emacs [37] and importing settings that easens their use, but thisis arguably an environment with a steep learning curve that isn’t very user friendlyfor beginners. Another promising tool that aims to be an all encompassing, whilestill user friendly, IDE is LightTable [39]. This tool is aimed primarily at Clojuredevelopment, but is today still in its beta stage and still in development.

The fact that there are no stable, all encompassing IDEs for Clojure developmentmeans that the developer gets less support in her development process than shewould when developing Java projects. This becomes especially striking when oneconsiders code refactoring and modularity. It’s not unusual that a developer realisesafter a while of coding that the code contains strong couplings. Separating thecouplings and thus creating more modular code would then require that the codeis refactored into separate parts with couplings between them. Most Java IDEssupport developers in this refactoring but the same support is lacking in Clojuredevelopment tools. Modularizing one’s already existing code is thus harder to do inClojure than in Java, which would imply that the highly modularized pattern thatthe TDD process requires in order to be practiced e�ectively is harder to achievein Clojure than in Java, when regarding the refactoring of code. One doesn’t getmuch support in creating and automatically running tests when lacking IDE supporteither, which would clearly have a negative impact on the TDD process.

5.2 Problems of debugging

Debugging is an essential part of the development process and it’s something thatone would rather spend as little time on as possible. The ability to pause a processduring its execution and inspect its state is arguably one of the most fundamentalparts of debugging. This is why most modern IDEs support the use of breakpoints- flags inserted into code that temporarily haults the execution of a program andallows developers to do just that.

Clojure development environments are lacking in the support of breakpoints.In the complementary source code to The Joy of Clojure [40] the author presentsa method of manually creating breakpoints through the use of a relatively simple

36

5.2. PROBLEMS OF DEBUGGING

macro. This does however require that breakpoints are inserted directly into thesource code rather than as flags in the editor. It also requires that the programis executed interactively through the REPL, which isn’t suitable for all circum-stances. Although a usable method in small projects it’s doubtful that it’s practicalto implement in larger projects.

The arguably most complete external tool suite for debugging Clojure code isRitz [41], an Emacs plugin that supports management and inspection of a programsstate during execution. Using an Emacs plugin isn’t however a suitable choice formany developers and the user friendliness of Ritz’s text-based interface is debatable.

As a program crashes during execution it’s useful to be able to extract infor-mation about what caused the crash. Clojure, like many other languages, providethis information through the presentation of a stack trace along with a simple errormessage. Consider the following function that (wrongly) tries to apply a number asa function:

(defn -main [](dorun (map 5 ["a" "b" "c"])))

This will cause an error during execution and the developer is provided with astacktrace:

Exception in thread "main" java.lang.ClassCastException:java.lang.Long cannot be cast to clojure.lang.IFnat clojure.core$map$fn__4207.invoke(core.clj:2485)at clojure.lang.LazySeq.sval(LazySeq.java:42)at clojure.lang.LazySeq.seq(LazySeq.java:60)at clojure.lang.RT.seq(RT.java:484)at clojure.core$seq.invoke(core.clj:133)at clojure.core$dorun.invoke(core.clj:2780)at st_example.core$_main.invoke(core.clj:3)at clojure.lang.Var.invoke(Var.java:411)at user$eval5$fn__7.invoke(form-init529821451174665787.clj:1)at user$eval5.invoke(form-init529821451174665787.clj:1)at clojure.lang.Compiler.eval(Compiler.java:6619)at clojure.lang.Compiler.eval(Compiler.java:6609)at clojure.lang.Compiler.load(Compiler.java:7064)at clojure.lang.Compiler.loadFile(Compiler.java:7020)at clojure.main$load_script.invoke(main.clj:294)at clojure.main$init_opt.invoke(main.clj:299)at clojure.main$initialize.invoke(main.clj:327)at clojure.main$null_opt.invoke(main.clj:362)at clojure.main$main.doInvoke(main.clj:440)at clojure.lang.RestFn.invoke(RestFn.java:421)at clojure.lang.Var.invoke(Var.java:419)at clojure.lang.AFn.applyToHelper(AFn.java:163)

37

CHAPTER 5. DISCUSSION

at clojure.lang.Var.applyTo(Var.java:532)at clojure.main.main(main.java:37)

As can be clearly seen this relatively simple error provides a long, cryptic stacktrace that reveals much of the internal workings of Clojure and which doesn’t helpthe developer much in tracing the source of the crash, especially when logic is nestedthrough several layers of function calls.

Overall one could argue that debugging Clojure code is much harder than de-bugging Java code, mainly due to the fact that there exists little IDE support forinspecting executing programs and error tracing. This is sure to a�ect the develop-ment process negatively, regardless of whether TDD is practiced or not.

5.3 Dynamic typing and productivityWhen modifying existing code from using one type of value to another one must instatically typed languages like Java change the type signature for all usages of thevalue, while none of that is necessary in dynamically typed languages like Clojure.The extra time it takes to change type signatures is insignificant, but it’s possiblethat the extra e�ort required for developers to make these changes is substantialenough for there to be a negative e�ect on their will to alter existing code.

If this is the case then it will have an especially negative impact on the TDDprocess because of the method of continuously making small but many modificationsof existing code that TDD promotes. The fact that Clojure is built on a core ofa very few basic types of data structures lessens the chance of type collissions andit’s thus argued that one might see an increase in productivity when developing inClojure as compared to Java in regards of choosing types for values in the sourcecode.

5.4 Mocking made easierThe introduction of mocking objects in Java came as an afterthought long afterthe language had been created and established in the industry and had to be doneso through external frameworks and not as part of the core language. Thoughit’s possible to create mocked objects without the use of frameworks in Java, thisbecomes so verbose that it’s arguably not practical.

The fact that the basic mockable component in Clojure is a function, rather thana class as in Java, means that mocked functionality is generally smaller. This in turnleads to the declaration of mocks being less verbose and easier to comprehend dueto their smaller scope. Couple this with the fact that idiomatic Clojure functionstend to be small, composable and preferably pure and we get an overall test designwhere mocks are smaller and simpler, which would have a positive e�ect on thecreation and maintenance of unit tests.

38

5.5. THE REPL AND AUTOMATED TESTS

5.5 The REPL and automated testsThe interactive REPL is a useful tool for exploring core functions and libraries inClojure and it’s also of use for developers when testing the behavior of their softwareas it’s being developed.

Manually testing software through the REPL has a drawback however in thesense that none of the code executed there will be stored as actual unit tests,which means that developers might easily forget to add test cases to the software’sautomated test suite. This will in turn lead to incomplete test suites and we knowthat this is bad for several reasons, especially when practicing TDD. The REPLmight thus lead to increased productivity as it’s easier for developers to interact withtheir software, but it may also lead to incomplete test suites and thus negativelyimpact the TDD process when developers forget to add and automate tests.

39

Chapter 6

Conclusions

Although Java and Clojure are two fundamentally di�erent languages this does nothave a big impact on how unit tests are created. It was observed that the basiccomponent with regards to code modularization is the class in Java and the functionin Clojure. This leads to di�erent implementations of the dependency injectionpattern where dependencies are injected directly into function calls in Clojure. Thedynamic typing of Clojure allows for mocked dependencies to be easily createdinlined in code without the need for external frameworks.

Logic placed in constructors of Java classes is di�cult to test at best anduntestable at worst. Clojure faces a similar problem stemming from a design fea-ture that allows code within a namespace to be executed when the namespace isloaded. Such code should be avoided whenever possible. Similar parallels can bedrawn between the testing of macros, where testing a macro is the same as testinga list transformation - a task that should feel comfortable for any experienced Javadeveloper.

There is currently a noticable deficit in the development tooling ecosystem ofClojure as compared to Java. The interactive REPL may possibly lead to increasedproductivity but there is a lack of extensive debugging tools and user friendly edi-tors which might negatively impact the TDD process by slowing down the flow ofdevelopment. Clojure has however not been around for long as an established pro-gramming language, so it’s possible that this may come to change in the foreseeablefuture.

41

Bibliography

[1] http://git-scm.com, Retrieved on 2014-09-11.

[2] Apache Software Foundation, http://subversion.apache.org, Retrieved on2014-09-11.

[3] K. Beck, Test Driven Development: By Example, 1st ed. Addison-Wesley, 2002.

[4] K. Beck, Extreme Programming Explained: Embrace Change. Addison-Wesley,1999.

[5] J. Humble, C. Read and D. North, "The Deployment Production Line," Proceed-ings of AGILE 2006 Conference, 2006.

[6] TIOBE Software BV, "TIOBE Index for August 2014," http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html, Retrieved on 2014-09-11.

[7] W. Royce, "Managing the Development of Large Software Systems," Proceedings,IEEE WESCON, pp. 1-9, Aug. 1970.

[8] D. Parnas and P. Clements, "A Rational Design Process: How and Why toFake It," IEEE Transactions on Software Engineering, Volume 12 Issue 2, pp.251-257, Feb. 1986.

[9] K. Beck et al., "Manifesto for Agile Software Development," http://agilemanifesto.org, Retrieved on 2014-09-11.

[10] J. Sutherland, "Agile Development: Lessons Learned From the First Scrum,"Cutter Agile Project Management Advisory Service. Executive Update, Volume5 Number 20, Oct. 2004.

[11] D. Anderson. Agile Management for Software Engineering: Applying the The-ory of Constraints for Business Results. Prentice Hall, 2003.

[12] L. Madeyski. Test-Driven Development - An Empirical Evaluation of AgilePractice. Springer, 2010.

[13] M. Fowler, "Inversion of Control Containers and the Dependency Injection pat-tern," http://www.martinfowler.com/articles/injection.html, Retrievedon 2014-09-11.

43

BIBLIOGRAPHY

[14] R. Martin, "The Principles of OOD", http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, Retrieved on 2014-09-11.

[15] N. Nagappan, E. Maximilien, T. Bhat and L. Williams, "Realizing QualityImprovement Through Test-Driven Development: Results and Experiences ofFour Industrial Teams, " Empirical Software Engineering, Volume 13 Issue 3,pp.289-302, 2008.

[16] H. Erdogmus, "On the E�ectiveness of Test-first Approach to Programming,"Proceedings of the IEEE Transactions on Software Engineering, Volume 31 Issue1, pp. 226-237, 2005.

[17] A. Miller, "A Dependency Injection Pattern in Clojure," http://tech.puredanger.com/2014/01/03/clojure-dependency-injection/, Retrievedon 2014-09-11.

[18] "International Draughts," http://en.wikipedia.org/wiki/International_draughts, Retrieved on 2014-09-11.

[19] T. Mackinnon, S. Freeman and P. Craig, "Endo-Testing: Unit Testing withMock Objects, " Extreme Programming Explained, pp. 287-301, Addison-Wesley,2001.

[20] S. Freeman. Growing Object-Oriented Software, Guided By Tests. Addison-Wesley, 2009.

[21] Clarius, Manas and InSTEDD, https://github.com/Moq/moq4, Retrieved on2014-09-11.

[22] S. Faber, https://code.google.com/p/mockito/, Retrieved on 2014-09-11.

[23] M. Fowler, "Mocks Aren’t Stubs, " http://martinfowler.com/articles/mocksArentStubs.html, Retrieved on 2014-09-11.

[24] https://github.com/marick/Midje, Retrieved on 2014-09-11.

[25] R. Hickey, "Rationale," http://clojure.org/rationale, Retrieved on 2014-09-11.

[26] R. Hickey, "State," http://clojure.org/state, Retrieved on 2014-09-11.

[27] R. Hickey, "Persistent Data Structures and Managed References," http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey, Retrievedon 2014-09-11.

[28] Oracle, http://www.oracle.com/technetwork/java/javase/documentation/index-jsp-135444.html, Retrieved on 2014-09-11.

44

BIBLIOGRAPHY

[29] J. McCarthy, "Recursive functions of symbolic expressions and their computa-tion by machine, Part I," Communications of the ACM, Volume 3 Issue 4, pp.184-195, Apr. 1960.

[30] http://clojurescript.net, Retrieved on 2014-09-11.

[31] https://github.com/clojure/clojure-clr, Retrieved on 2014-09-11.

[32] R. Hickey, "Clojure 1.0", http://clojure.blogspot.se/2009/05/clojure-10.html, Retrieved on 2014-09-11.

[33] "Expert to Expert: Rich Hickey and Brian Beckman - In-side Clojure," http://channel9.msdn.com/shows/Going+Deep/Expert-to-Expert-Rich-Hickey-and-Brian-Beckman-Inside-Clojure/,Oct. 2009, Retrieved on 2014-09-11.

[34] T. Dogsa, D. Batic "The e�ectiveness of test-driven development: an industrialcase study," Software Quality Journal, Volume 19 Issue 4, pp. 643-661, 2011.

[35] The Eclipse Foundation, https://www.eclipse.org/home/index.php, Re-trieved on 2014-09-11.

[36] JetBrains, http://www.jetbrains.com/idea/, Retrieved on 2014-09-11.

[37] Free Software Foundation, http://www.gnu.org/software/emacs/, Retrievedon 2014-09-11.

[38] Phil Hagelberg, http://leiningen.org, Retrieved on 2014-09-11.

[39] Kodowa, http://www.lighttable.com, Retrieved on 2014-09-11.

[40] M. Fogus and C. Houser, https://github.com/joyofclojure/book-source,Retrieved on 2014-09-11.

[41] H. Duncan, https://github.com/pallet/ritz, Retrieved on 2014-09-11.

45

www.kth.se