Difference between revisions of "FND-CSC-Test driven development"
m |
m |
||
(5 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | <div id=" | + | <div id="ABC"> |
− | + | <div style="padding:5px; border:1px solid #000000; background-color:#b3dbce; font-size:300%; font-weight:400; color: #000000; width:100%;"> | |
− | + | Test Driven Development | |
− | + | <div style="padding:5px; margin-top:20px; margin-bottom:10px; background-color:#b3dbce; font-size:30%; font-weight:200; color: #000000; "> | |
− | + | (Test Driven Development) | |
− | + | </div> | |
− | |||
− | |||
− | |||
− | |||
</div> | </div> | ||
− | {{ | + | {{Smallvspace}} |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | < | + | <div style="padding:5px; border:1px solid #000000; background-color:#b3dbce33; font-size:85%;"> |
− | <div | + | <div style="font-size:118%;"> |
− | + | <b>Abstract:</b><br /> | |
<section begin=abstract /> | <section begin=abstract /> | ||
− | + | Test Driven Development (TDD) is a powerful methodology to help writing correct code. Unit testing is its cornerstone. Integration testing ensures units work together. | |
− | ... | ||
<section end=abstract /> | <section end=abstract /> | ||
− | + | </div> | |
− | + | <!-- ============================ --> | |
− | + | <hr> | |
− | + | <table> | |
− | == | + | <tr> |
− | === | + | <td style="padding:10px;"> |
− | < | + | <b>Objectives:</b><br /> |
− | <!-- | + | This unit will ... |
− | + | * ... introduce the concept of Test Driven Devlopment; | |
+ | * ... introduce unit testing and integration testing, two cornerstones of building robust software for reproducible results. | ||
+ | </td> | ||
+ | <td style="padding:10px;"> | ||
+ | <b>Outcomes:</b><br /> | ||
+ | After working through this unit you ... | ||
+ | * ... begin to use TDD in your own practice; | ||
+ | * ... have enough context to learn more about unit testing and integration testing in practice. | ||
+ | </td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | <!-- ============================ --> | ||
+ | <hr> | ||
+ | <b>Deliverables:</b><br /> | ||
+ | <section begin=deliverables /> | ||
+ | <li><b>Time management</b>: Before you begin, estimate how long it will take you to complete this unit. Then, record in your course journal: the number of hours you estimated, the number of hours you worked on the unit, and the amount of time that passed between start and completion of this unit.</li> | ||
+ | <li><b>Journal</b>: Document your progress in your [[FND-Journal|Course Journal]]. Some tasks may ask you to include specific items in your journal. Don't overlook these.</li> | ||
+ | <li><b>Insights</b>: If you find something particularly noteworthy about this unit, make a note in your [[ABC-Insights|'''insights!''' page]].</li> | ||
+ | <section end=deliverables /> | ||
+ | <!-- ============================ --> | ||
+ | <hr> | ||
+ | <section begin=prerequisites /> | ||
+ | <b>Prerequisites:</b><br /> | ||
+ | This unit builds on material covered in the following prerequisite units:<br /> | ||
*[[FND-CSC-Software_development|FND-CSC-Software_development (Software Development for Research Labs)]] | *[[FND-CSC-Software_development|FND-CSC-Software_development (Software Development for Research Labs)]] | ||
+ | <section end=prerequisites /> | ||
+ | <!-- ============================ --> | ||
+ | </div> | ||
− | {{ | + | {{Smallvspace}} |
− | |||
− | |||
− | |||
− | |||
− | |||
+ | {{Smallvspace}} | ||
− | |||
− | |||
− | |||
− | + | __TOC__ | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
{{Vspace}} | {{Vspace}} | ||
Line 72: | Line 66: | ||
=== Evaluation === | === Evaluation === | ||
− | |||
− | |||
<b>Evaluation: NA</b><br /> | <b>Evaluation: NA</b><br /> | ||
− | :This unit is not evaluated for course marks. | + | <div style="margin-left: 2rem;">This unit is not evaluated for course marks.</div> |
− | + | == Contents == | |
− | |||
+ | ===Test Driven Development=== | ||
− | + | TDD is a development methodology to ensure that code actually does what it is meant to do. In practice, in TDD we define our software goals and devise a test (or battery of tests) for each. Initially, all tests fail. As we develop, the tests succeed. As we continue development | |
− | |||
− | |||
− | |||
− | |||
− | TDD is | ||
* we think carefully about how to break the project into components and structure them (units). These more or less do one thing, one thing only, and don't have side-effects; | * we think carefully about how to break the project into components and structure them (units). These more or less do one thing, one thing only, and don't have side-effects; | ||
* we discipline ourselves to watch out for unexpected input, edge- and corner cases and unwarranted assumptions; | * we discipline ourselves to watch out for unexpected input, edge- and corner cases and unwarranted assumptions; | ||
* we can be confident that later changes do not break what we have done earlier - because our tests keep track of the behaviour. | * we can be confident that later changes do not break what we have done earlier - because our tests keep track of the behaviour. | ||
+ | |||
+ | Note that TDD is not meant to be a method to find bugs - although it does that too. It is a design process. It helps you make your requirements explicit, and structure your code well. The key contribution of TDD is that it prompts developers to clearly identify testable behaviour of their code. | ||
{{Vspace}} | {{Vspace}} | ||
Line 98: | Line 87: | ||
* However: Code may be correct, but still unusable in practice. Without {{WP|Software_performance_testing|'''performance testing'''}} the development cannot be considered to be complete. | * However: Code may be correct, but still unusable in practice. Without {{WP|Software_performance_testing|'''performance testing'''}} the development cannot be considered to be complete. | ||
− | Testing supports '''maintenance'''. When you find a bug, write a test that fails because of the bug, then fix the bug, and with great satisfaction watch your test pass. Also, be mindful that you may have made the same type of error elsewhere in your code. Search for these cases, write tests and fix them too. | + | Testing supports '''maintenance'''. When you find a bug, write a test that fails because of the bug, then fix the bug, and with great satisfaction watch your test pass. Should anything of the sort happen again, your test will notice. Also, be mindful that you may have made the same type of error elsewhere in your code. Search for these cases, write tests and fix them too. |
− | |||
− | |||
− | |||
− | |||
+ | That said, one of the strongest points of TDD is that it supports refactoring! Work in a cycle: Write tests → Develop → Refactor. This allows you to get something working quickly, then adopting more elegant / efficient / maintainable solutions - and as you do that, your tests ensure you do not break functionality by mistake. | ||
{{Vspace}} | {{Vspace}} | ||
+ | ===Unit testing=== | ||
− | + | {{WP|Unit_testing|Unit tests}} focus on the individual code '''units''' - the basic functions. They ensure that each function | |
− | + | * matches its specifications; | |
− | + | * handles erroneous input gracefully; | |
− | + | * fails gracefully when it can't recover. | |
− | + | In addition, '''automated''' unit tests | |
+ | *ensure that ongoing development does not break existing functions. | ||
+ | To be most useful, unit tests test '''only one''' function's behaviour, and not its integration with other functions. That is because functions need to be reconfigured when code is refactored, and tests that inadvertently test two or more functions, or the dependence of a function on some specific input, create '''dependencies'''. If you succeed identifying such dependencies explicitly, and adding them to your '''integration tests''' instead, you will have come a long way towards developing maintainable and extensible code. More on this topic in Steve Sanderson's blog post, linked below. | ||
− | + | Unit tests usually employ some "testing framework" (eg. the <code>testthat</code> package in R) that supports writing simple statements, which compare observed behaviour with expected values, and can be automatically executed. | |
− | |||
− | < | ||
− | < | ||
{{Vspace}} | {{Vspace}} | ||
+ | ===Integration testing=== | ||
− | + | In practice, unit tests and {{WP|Integration_testing|integration tests}} are written in the same frameworks, but they have very different goals and should be clearly separated, since integration tests need to be change when code is reconfigured. The goal of integration tests is to ensure the integrity of the interfaces that have been defined between fiucntions or code modules - e.g. the structure and validity of R objects that are passed between functions, or input/output files. The may also test the collaboration of functions with small, synthetic data sets that give known-to-be-correct results. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Ideally, integration tests reflect the architecture of the project, e.g. the data-flow, or workflow. Of course, this is a dialectic relationship, and designing integration tests can do as much for the architecture design as designing unit tests can do for designing a function. For example, designing and implementing integration tests ensures that the states of a workflow are properly exposed, observable, and testable. | |
− | |||
− | - | ||
{{Vspace}} | {{Vspace}} | ||
+ | == Further reading, links and resources == | ||
+ | <div class="reference-box">[https://stackoverflow.com/questions/520064/what-is-unit-test-integration-test-smoke-test-regression-test Brief overview and comparison of testing categories and objectives] (stackoverflow).</div> | ||
+ | <div class="reference-box">http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/ Writing Great Unit Tests: Best and Worst Practices.] Sanderson's blog post discusses the distinction between unit tests, integration tests, and the "dirty hybrids" inbetween.</div> | ||
− | + | <div class="reference-box">[http://blog.iese.fraunhofer.de/architecture-centric-integration-testing/ Architecture Centric Integration Testing] - a blog post by Dr.'s Elberzhager and Naab of the Fraunhofer Intitute for Experimental Software Engineering.</div> | |
− | + | <!-- {{#pmid: 19957275}} --> | |
− | <!-- | + | <!-- {{WWW|WWW_GMOD}} --> |
− | + | <!-- --> | |
− | ---- | + | == Notes == |
+ | <references /> | ||
{{Vspace}} | {{Vspace}} | ||
− | |||
− | |||
− | |||
− | |||
− | |||
<div class="about"> | <div class="about"> | ||
Line 172: | Line 141: | ||
:2017-08-05 | :2017-08-05 | ||
<b>Modified:</b><br /> | <b>Modified:</b><br /> | ||
− | :2017- | + | :2017-10-15 |
<b>Version:</b><br /> | <b>Version:</b><br /> | ||
− | :0 | + | :1.0 |
<b>Version history:</b><br /> | <b>Version history:</b><br /> | ||
+ | *1.0 First live version | ||
*0.1 First stub | *0.1 First stub | ||
</div> | </div> | ||
− | |||
− | |||
{{CC-BY}} | {{CC-BY}} | ||
+ | [[Category:ABC-units]] | ||
+ | {{UNIT}} | ||
+ | {{LIVE}} | ||
</div> | </div> | ||
<!-- [END] --> | <!-- [END] --> |
Latest revision as of 13:13, 24 September 2020
Test Driven Development
(Test Driven Development)
Abstract:
Test Driven Development (TDD) is a powerful methodology to help writing correct code. Unit testing is its cornerstone. Integration testing ensures units work together.
Objectives:
|
Outcomes:
|
Deliverables:
Prerequisites:
This unit builds on material covered in the following prerequisite units:
Contents
Evaluation
Evaluation: NA
Contents
Test Driven Development
TDD is a development methodology to ensure that code actually does what it is meant to do. In practice, in TDD we define our software goals and devise a test (or battery of tests) for each. Initially, all tests fail. As we develop, the tests succeed. As we continue development
- we think carefully about how to break the project into components and structure them (units). These more or less do one thing, one thing only, and don't have side-effects;
- we discipline ourselves to watch out for unexpected input, edge- and corner cases and unwarranted assumptions;
- we can be confident that later changes do not break what we have done earlier - because our tests keep track of the behaviour.
Note that TDD is not meant to be a method to find bugs - although it does that too. It is a design process. It helps you make your requirements explicit, and structure your code well. The key contribution of TDD is that it prompts developers to clearly identify testable behaviour of their code.
Typically testing is done at several levels:
- During the initial development phases unit testing continuously checks the function of the software units of the system.
- As the code base progresses, code units are integrated and begin interacting via their interfaces - we begin integration testing. Interfaces can be specified as "contracts" that define the conditions and obligations of an interaction. Typically, a contract will define the precondition, postcondition and invariants of an interaction. Focussing on these aspects of system behaviour is also called design by contract. The primary task of integration testing is to verify that contracts are accurately and completely fulfilled.
- Finally validation tests verify the code, and validate its correct execution - just like a positive control in a lab experiment.
- However: Code may be correct, but still unusable in practice. Without performance testing the development cannot be considered to be complete.
Testing supports maintenance. When you find a bug, write a test that fails because of the bug, then fix the bug, and with great satisfaction watch your test pass. Should anything of the sort happen again, your test will notice. Also, be mindful that you may have made the same type of error elsewhere in your code. Search for these cases, write tests and fix them too.
That said, one of the strongest points of TDD is that it supports refactoring! Work in a cycle: Write tests → Develop → Refactor. This allows you to get something working quickly, then adopting more elegant / efficient / maintainable solutions - and as you do that, your tests ensure you do not break functionality by mistake.
Unit testing
Unit tests focus on the individual code units - the basic functions. They ensure that each function
- matches its specifications;
- handles erroneous input gracefully;
- fails gracefully when it can't recover.
In addition, automated unit tests
- ensure that ongoing development does not break existing functions.
To be most useful, unit tests test only one function's behaviour, and not its integration with other functions. That is because functions need to be reconfigured when code is refactored, and tests that inadvertently test two or more functions, or the dependence of a function on some specific input, create dependencies. If you succeed identifying such dependencies explicitly, and adding them to your integration tests instead, you will have come a long way towards developing maintainable and extensible code. More on this topic in Steve Sanderson's blog post, linked below.
Unit tests usually employ some "testing framework" (eg. the testthat
package in R) that supports writing simple statements, which compare observed behaviour with expected values, and can be automatically executed.
Integration testing
In practice, unit tests and integration tests are written in the same frameworks, but they have very different goals and should be clearly separated, since integration tests need to be change when code is reconfigured. The goal of integration tests is to ensure the integrity of the interfaces that have been defined between fiucntions or code modules - e.g. the structure and validity of R objects that are passed between functions, or input/output files. The may also test the collaboration of functions with small, synthetic data sets that give known-to-be-correct results.
Ideally, integration tests reflect the architecture of the project, e.g. the data-flow, or workflow. Of course, this is a dialectic relationship, and designing integration tests can do as much for the architecture design as designing unit tests can do for designing a function. For example, designing and implementing integration tests ensures that the states of a workflow are properly exposed, observable, and testable.
Further reading, links and resources
Notes
About ...
Author:
- Boris Steipe <boris.steipe@utoronto.ca>
Created:
- 2017-08-05
Modified:
- 2017-10-15
Version:
- 1.0
Version history:
- 1.0 First live version
- 0.1 First stub
This copyrighted material is licensed under a Creative Commons Attribution 4.0 International License. Follow the link to learn more.