Open main menu
Home
Random
Recent changes
Special pages
Community portal
Preferences
About Wikipedia
Disclaimers
Incubator escapee wiki
Search
User menu
Talk
Dark mode
Contributions
Create account
Log in
Editing
Test-driven development
(section)
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
== Development style == There are various aspects to using test-driven development, for example the principles of "keep it simple, stupid" ([[KISS principle|KISS]]) and "[[You aren't gonna need it]]" (YAGNI). By focusing on writing only the code necessary to pass tests, designs can often be cleaner and clearer than is achieved by other methods.<ref name=Beck /> In ''Test-Driven Development by Example'', Kent Beck also suggests the principle "[[Fake it till you make it]]". To achieve some advanced design concept such as a [[design pattern]], tests are written that generate that design. The code may remain simpler than the target pattern, but still pass all required tests. This can be unsettling at first but it allows the developer to focus only on what is important. Writing the tests first: The tests should be written before the functionality that is to be tested. This has been claimed to have many benefits. It helps ensure that the application is written for testability, as the developers must consider how to test the application from the outset rather than adding it later. It also ensures that tests for every feature gets written. Additionally, writing the tests first leads to a deeper and earlier understanding of the product requirements, ensures the effectiveness of the test code, and maintains a continual focus on [[software quality]].<ref name="Pathfinder Solutions">{{cite web|title=Effective TDD for Complex Embedded Systems Whitepaper|url=http://www.pathfindersolns.com/wp-content/uploads/2012/05/Effective-TDD-Executive-Summary.pdf|archive-url=https://web.archive.org/web/20160316153308/http://www.pathfindersolns.com/wp-content/uploads/2012/05/Effective-TDD-Executive-Summary.pdf|url-status=dead|archive-date=2016-03-16|publisher=Pathfinder Solutions}}</ref> When writing feature-first code, there is a tendency by developers and organizations to push the developer on to the next feature, even neglecting testing entirely. The first TDD test might not even compile at first, because the classes and methods it requires may not yet exist. Nevertheless, that first test functions as the beginning of an executable specification.<ref>{{cite web |url=http://www.agilesherpa.org/agile_coach/engineering_practices/test_driven_development/ |title=Agile Test Driven Development |publisher=Agile Sherpa |date=2010-08-03 |access-date=2012-08-14 |archive-url=https://archive.today/20120723184333/http://www.agilesherpa.org/agile_coach/engineering_practices/test_driven_development/ |archive-date=2012-07-23 |url-status=dead }}</ref> Each test case fails initially: This ensures that the test really works and can catch an error. Once this is shown, the underlying functionality can be implemented. This has led to the "test-driven development mantra", which is "red/green/refactor", where red means ''fail'' and green means ''pass''. Test-driven development constantly repeats the steps of adding test cases that fail, passing them, and refactoring. Receiving the expected test results at each stage reinforces the developer's mental model of the code, boosts confidence and increases productivity. ===Code visibility=== Test code needs access to the code it is testing, but testing should not compromise normal design goals such as [[information hiding]], encapsulation and the [[separation of concerns]]. Therefore, unit test code is usually located in the same project or [[Module (programming)|module]] as the code being tested. In [[object oriented design]] this still does not provide access to private data and methods. Therefore, extra work may be necessary for unit tests. In [[Java (programming language)|Java]] and other languages, a developer can use [[Reflection (computer science)|reflection]] to access private fields and methods.<ref>{{cite web |title=Subverting Java Access Protection for Unit Testing |url=http://www.onjava.com/pub/a/onjava/2003/11/12/reflection.html |last=Burton |first=Ross |date=2003-11-12 |publisher=O'Reilly Media, Inc. |access-date=2009-08-12 }}</ref> Alternatively, an [[inner class]] can be used to hold the unit tests so they have visibility of the enclosing class's members and attributes. In the [[.NET Framework]] and some other programming languages, [[partial class]]es may be used to expose private methods and data for the tests to access. It is important that such testing hacks do not remain in the production code. In [[C (programming language)|C]] and other languages, [[Directive (programming)|compiler directives]] such as <code>#if DEBUG ... #endif</code> can be placed around such additional classes and indeed all other test-related code to prevent them being compiled into the released code. This means the released code is not exactly the same as what was unit tested. The regular running of fewer but more comprehensive, end-to-end, integration tests on the final release build can ensure (among other things) that no production code exists that subtly relies on aspects of the test harness. There is some debate among practitioners of TDD, documented in their blogs and other writings, as to whether it is wise to test private methods and data anyway. Some argue that private members are a mere implementation detail that may change, and should be allowed to do so without breaking numbers of tests. Thus it should be sufficient to test any class through its public interface or through its subclass interface, which some languages call the "protected" interface.<ref>{{cite web | url=https://www.python.org/dev/peps/pep-0008/ |title=PEP 8 -- Style Guide for Python Code |last1=van Rossum |first1=Guido |last2=Warsaw |first2=Barry |date=5 July 2001 |publisher=Python Software Foundation |access-date=6 May 2012}}</ref> Others say that crucial aspects of functionality may be implemented in private methods and testing them directly offers advantage of smaller and more direct unit tests.<ref>{{cite web |url=http://blogs.msdn.com/jamesnewkirk/archive/2004/06/07/150361.aspx |title=Testing Private Methods/Member Variables - Should you or shouldn't you |last=Newkirk |first=James |date=7 June 2004 |publisher=Microsoft Corporation |access-date=2009-08-12}}</ref><ref>{{cite web |url=http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx |title=How to Test Private and Protected methods in .NET |last=Stall |first=Tim |date=1 Mar 2005 |publisher=CodeProject |access-date=2009-08-12}}</ref> ===Fakes, mocks and integration tests=== Unit tests are so named because they each test ''one unit'' of code. A complex module may have a thousand unit tests and a simple module may have only ten. The unit tests used for TDD should never cross process boundaries in a program, let alone network connections. Doing so introduces delays that make tests run slowly and discourage developers from running the whole suite. Introducing dependencies on external modules or data also turns ''unit tests'' into ''integration tests''. If one module misbehaves in a chain of interrelated modules, it is not so immediately clear where to look for the cause of the failure. When code under development relies on a database, a web service, or any other external process or service, enforcing a unit-testable separation is also an opportunity and a driving force to design more modular, more testable and more reusable code.<ref>{{cite book |title=Refactoring - Improving the design of existing code |last=Fowler |first=Martin |year=1999 |publisher=Addison Wesley Longman, Inc. |location=Boston |isbn=0-201-48567-2 |url=https://archive.org/details/isbn_9780201485677 }}</ref> Two steps are necessary: # Whenever external access is needed in the final design, an [[Interface (computer science)|interface]] should be defined that describes the access available. See the [[dependency inversion principle]] for a discussion of the benefits of doing this regardless of TDD. # The interface should be implemented in two ways, one of which really accesses the external process, and the other of which is a [[mock object|fake or mock]]. Fake objects need do little more than add a message such as "Person object saved" to a [[Tracing (software)|trace log]], against which a test [[Assertion (computing)|assertion]] can be run to verify correct behaviour. Mock objects differ in that they themselves contain [[Assertion (computing)|test assertions]] that can make the test fail, for example, if the person's name and other data are not as expected. Fake and mock object methods that return data, ostensibly from a data store or user, can help the test process by always returning the same, realistic data that tests can rely upon. They can also be set into predefined fault modes so that error-handling routines can be developed and reliably tested. In a fault mode, a method may return an invalid, incomplete or [[Null character|null]] response, or may throw an [[Exception handling|exception]]. Fake services other than data stores may also be useful in TDD: A fake encryption service may not, in fact, encrypt the data passed; a fake random number service may always return 1. Fake or mock implementations are examples of [[dependency injection]]. A test double is a test-specific capability that substitutes for a system capability, typically a class or function, that the UUT depends on. There are two times at which test doubles can be introduced into a system: link and execution. Link time substitution is when the test double is compiled into the load module, which is executed to validate testing. This approach is typically used when running in an environment other than the target environment that requires doubles for the hardware level code for compilation. The alternative to linker substitution is run-time substitution in which the real functionality is replaced during the execution of a test case. This substitution is typically done through the reassignment of known function pointers or object replacement. Test doubles are of a number of different types and varying complexities: * [[Dummy code|Dummy]] β A dummy is the simplest form of a test double. It facilitates linker time substitution by providing a default return value where required. * [[Method stub|Stub]] β A stub adds simplistic logic to a dummy, providing different outputs. * Spy β A spy captures and makes available parameter and state information, publishing accessors to test code for private information allowing for more advanced state validation. * [[Mock object|Mock]] β A mock is specified by an individual test case to validate test-specific behavior, checking parameter values and call sequencing. * Simulator β A simulator is a comprehensive component providing a higher-fidelity approximation of the target capability (the thing being doubled). A simulator typically requires significant additional development effort.<ref name="Pathfinder Solutions" /> A corollary of such dependency injection is that the actual database or other external-access code is never tested by the TDD process itself. To avoid errors that may arise from this, other tests are needed that instantiate the test-driven code with the "real" implementations of the interfaces discussed above. These are [[Integration testing|integration tests]] and are quite separate from the TDD unit tests. There are fewer of them, and they must be run less often than the unit tests. They can nonetheless be implemented using the same testing framework. Integration tests that alter any [[persistent storage|persistent store]] or database should always be designed carefully with consideration of the initial and final state of the files or database, even if any test fails. This is often achieved using some combination of the following techniques: * The <code>TearDown</code> method, which is integral to many test frameworks. * <code>try...catch...finally</code> [[exception handling]] structures where available. * [[Database transactions]] where a transaction [[atomicity (database systems)|atomically]] includes perhaps a write, a read and a matching delete operation. * Taking a "snapshot" of the database before running any tests and rolling back to the snapshot after each test run. This may be automated using a framework such as [[Apache Ant|Ant]] or [[NAnt]] or a [[continuous integration]] system such as [[CruiseControl]]. * Initialising the database to a clean state ''before'' tests, rather than cleaning up ''after'' them. This may be relevant where cleaning up may make it difficult to diagnose test failures by deleting the final state of the database before detailed diagnosis can be performed. ===Keep the unit small=== For TDD, a unit is most commonly defined as a class, or a group of related functions often called a module. Keeping units relatively small is claimed to provide critical benefits, including: * Reduced debugging effort β When test failures are detected, having smaller units aids in tracking down errors. * Self-documenting tests β Small test cases are easier to read and to understand.<ref name="Pathfinder Solutions" /> Advanced practices of test-driven development can lead to [[acceptance testβdriven development]] (ATDD) and [[specification by example]] where the criteria specified by the customer are automated into acceptance tests, which then drive the traditional unit test-driven development (UTDD) process.<ref name="Koskela">Koskela, L. "Test Driven: TDD and Acceptance TDD for Java Developers", Manning Publications, 2007</ref> This process ensures the customer has an automated mechanism to decide whether the software meets their requirements. With ATDD, the development team now has a specific target to satisfy β the acceptance tests β which keeps them continuously focused on what the customer really wants from each user story.
Edit summary
(Briefly describe your changes)
By publishing changes, you agree to the
Terms of Use
, and you irrevocably agree to release your contribution under the
CC BY-SA 4.0 License
and the
GFDL
. You agree that a hyperlink or URL is sufficient attribution under the Creative Commons license.
Cancel
Editing help
(opens in new window)