Test-driven development

from Wikipedia, the free encyclopedia
Typical test-driven development process

Test-driven development (also test-driven programming ; English test first development or test-driven development , TDD ) is a method that is often used in the agile development of computer programs . With test-driven development, the programmer consistently creates software tests before the components to be tested.

Reasons for introducing test-driven development

According to the classic procedure, for example according to the waterfall or the V-model , tests are developed parallel to and independently of the system to be tested or even according to it. This often leads to the desired and required test coverage not being achieved. Possible reasons for this include:

  • Missing or insufficient testability of the system (monolithic, use of third-party components, ...).
  • Prohibition of investment in non-functional program parts on the part of the company management. ("Work that you won't see later in the program is wasted.")
  • Creation of tests under time pressure.
  • Negligence and lack of discipline on the part of the programmer when creating tests.

Another disadvantage of classic white box tests is that the developer knows the system to be tested and its peculiarities himself and as a result tests unintentionally "around errors" due to operational blindness.

The test-driven development method tries to counteract the reasons for insufficient test coverage and some of the disadvantages of white box tests.

method

In the test-driven development between testing on a small scale (is unit testing ) / unit testing and testing in the Great ( integration testing , system testing , acceptance testing ) to distinguish, with Beck's method is designed for unit testing.

Test-driven development with unit tests

tests first

Unit tests are written before the actual computer program. It is not specified whether the programmer who will do the implementation will also write the unit tests. It is allowed that several failed unit tests exist at the same time. The implementation of the behavior required by a unit test in the computer program can be postponed.

The tests first method can be viewed as a preliminary stage of test-driven development.

TDD after Kent Beck

Unit tests and units tested with them are always developed in parallel. The actual programming takes place in small, repeated micro-iterations. Such an iteration, which should only last a few minutes, has three main parts, which are called in the English Red-Green Refactor

  1. Red: Write a test that should check a new behavior (functionality) to be programmed. You start with the simplest example. If the function is older, this can also be a known error or a new functionality to be implemented. This test is initially not fulfilled by the existing program code , so it must fail.
  2. Green: Change the program code with as little effort as possible and add to it until it passes all tests after the test run that is then initiated.
  3. Then clean up the code ( refactoring ): Remove repetitions ( code duplication ), abstract where necessary, align it with the binding code conventions , etc. In this phase, no new behavior - which would then not be covered by tests - be introduced. The tests are carried out after each change; if they fail, the apparently faulty change cannot be transferred to the code used. The aim of the cleanup is to make the code simple and understandable .

These three steps are repeated until the known errors have been fixed, the code provides the desired functionality and the developer cannot think of any further useful tests that might still fail. The technical program unit (unit) treated in this way is then regarded as ready for the time being. The tests created together with her are retained so that, even after future changes, tests can be carried out to determine whether the aspects of behavior that have already been achieved are still met.

So that the changes - also called transformations - lead to the goal in step 2, each change must lead to a more general solution; it must not only treat the current test case at the expense of others. Tests that go into more and more detail drive the code towards an increasingly general solution. Observing the transformation priorities regularly leads to more efficient algorithms.

Consistently following this procedure is an evolutionary design method in that each of the individual changes evolves the system.

The core problem: effort objection and counter objections

The main objection to the procedure described is the supposedly high effort.

The idea described makes use of the fact that the intellectual effort that is invested in the constructive description, i.e. the program, during programming, and that makes up the main part of the programming time (in relation to the typing effort, for example), is a list of individual points and cases to be fulfilled includes. With just a little more effort, the case to be covered can be described exactly at this point in time before programming; the previous writing of a few test lines can even lead to better mental structuring and higher code quality. Second, test-driven development leads to a certain discipline, which functions are implemented in which order, because you first have to consider a test case, and thus potentially to a higher consideration of the customer benefit, see also YAGNI .

Unit tests or automated tests in general are often described as the safety net of a program in the event of necessary changes; without a high test coverage , a software system is fundamentally more prone to errors and problems in further development and maintenance.

Even with the first creation, the effort with TDD with a little practice to fulfill a certain functionality can be less than the effort of a supposedly quick solution without automated tests. According to the consensus, this applies all the more, the more durable the system is and therefore repeatedly subject to changes. The effort to write automated tests afterwards is much higher because the individual requirements and program lines have to be analyzed again, and a test coverage comparable to that of TDD is hardly realistic for reasons of effort and cost.

Test-driven development with system tests

System tests are always developed or at least specified before the system itself. In the case of test-driven development, the task of system development is no longer to meet requirements formulated in writing, as is traditionally the case, but to pass specified system tests.

Test-driven development with acceptance tests

Acceptance test-driven development (ATDD) is related to test-driven development, but differs in the procedure from test-driven development. Acceptance test-driven development is a communication tool between the customer or the users, the developers and the testers, which should ensure that the requirements are well described. Acceptance test-driven development does not require automation of the test cases, although this would be helpful for regression testing . In the case of acceptance test-driven development, the tests must also be readable for non-developers. The tests of test-driven development can in many cases be derived from the tests of acceptance test-driven development.

Similarities of test-driven development with system tests and unit tests

With both types of tests, the aim is as complete a test automation as possible . For test-driven development , all tests must be able to be carried out easily (“at the push of a button”) and as quickly as possible. For unit tests this means a duration of a few seconds, for system tests of a maximum of a few minutes, or only in exceptional cases longer.

The great advantages of the test-driven method over the classic are:

  • You have a Boolean metric for fulfilling the requirements: the tests are passed or not.
  • Refactoring, i.e. cleaning up the code, leads to fewer errors; Because you proceed in small steps and always along passed tests, few new errors occur and they are easier to localize.
  • Because testing can be carried out easily and without spending a great deal of time, the programmers work most of the time on a correct system and therefore with confidence and focus on the current subtask. (No "crossing the desert", no "everything is related to everything")
  • The system also documents the number of unit tests . You create an “executable specification” at the same time - what the software system is supposed to do is available in the form of tests that can be read and run at any time.
  • A test-driven procedure tends to lead to program code that is more modularized and easier to change and expand. The planned system is developed in small steps that are written and tested independently and only then integrated. The corresponding software units (classes, modules, ...) become smaller, more specific, their coupling becomes looser and their interfaces become simpler. If you also use mock objects , this also forces you to reduce dependencies or keep them simple, because otherwise the essential quick and easy exchange of modules for test and production code would not be possible.
  • Empirical studies have shown a lower defect rate due to TDD in inexperienced developers. However, this is offset by a higher expenditure of time. Other empirical studies could not determine any quality gain. Overall, the result is a mixed picture of the actual quality gain through TDD alone.

Areas of application

Test-driven development is an essential part of Extreme Programming (XP) and other agile methods . It can also be found outside of these, often in connection with pair programming . As an exercise method are often Katas used.

Tools

The test-driven development is urgent

  • a tool for build automation such as CruiseControl or Jenkins
  • a framework and a tool for test development and automation,

so that the iterations can be run through quickly and easily.

In Java development, Ant , Maven or Gradle and JUnit are mostly used for this . Similar tools exist for most other programming languages, such as: B. PHP PHPUnit or Ceedling , Unity and CMock for C .

For complex systems, several sub-components have to be developed independently of each other and third-party components are also used, such as a database system for the purpose of persistent data storage. The correct cooperation and function of the components in the system must then also be tested. In order to be able to test the individual components separately, which, however, depend essentially on other components for their correct function, mock objects are used as their substitutes. The mock objects replace and simulate the required other components in the test in a way that the tester programs them into.

A tool for acceptance tests and system tests is, for example, Framework for Integrated Test . A popular FIT variant is Fitnesse , a wiki server with an integrated test creation and test execution environment.

criticism

Consistency is necessary

The method of test-driven development can also be used incorrectly and then fail. Programmers who have no experience with it sometimes find it difficult or even impossible. You're wondering how to test something that isn't there yet. The effect can be that you neglect the principles of this method, which can lead to difficulties in the development process or even its collapse, especially with agile methods such as extreme programming. Without sufficient unit tests, sufficient test coverage for refactoring and the desired quality will not be achieved. This can be counteracted with pair programming and training.

Training / practice required

A key argument from opponents of test-driven development is that unit tests in particular unnecessarily increase the effort required to make changes to existing functionality, because a change to the production code causes a disproportionate number of unit tests to fail. The reason for this, however, is usually that the tested unit was not sufficiently separated, i.e. the tests are not atomic.

In order to avoid this problem, it is necessary that the programmers are trained in how to break down the requirements into atomic functional units and practice this.

No substitute for any other test type

Even this type of programming, which relies heavily on tests, like all test types, cannot reveal every error:

  • Errors that arise in the interaction between different programs or program parts can be found more easily using integration tests than using test-driven development
  • The usability of software cannot be determined by means of test-driven development. This are usability tests more appropriate.
  • The correspondence between the software and the functional and non-functional requirements can often not be determined by means of test-driven development. For this, acceptance test-driven development such as behavior-driven development or system tests are advisable.

None of the test types and procedures mentioned can reveal all errors, so in most cases several test types and error-avoiding procedures should be used.

literature

Web links

Individual evidence

  1. Robert C. Martin in: The Transformation Priority Premise (accessed on February 2, 2016) " http://blog.8thlight.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html "
  2. ^ Robert C. Martin : Clean Code: Refactoring, Patterns, Testing, and Techniques for Clean Code . mitp-Verlag, ISBN 978-0-13-235088-4 .
  3. Ken Pugh: Lean-Agile Acceptance Test-Driven Development: Better Software Through Collaboration . Addison-Wesley Professional, Boston 2011, ISBN 978-0-321-71408-4 (English).
  4. ^ Andy Oram, Greg Wilson et al .: Making Software - What Really Works And Why We Believe It. 1st edition. O'Reilly Media, 2010, ISBN 978-0-596-80832-7 .
  5. http://www.throwtheswitch.org/#download-section (English) (accessed January 20, 2017)