Unit testing: Difference between revisions
imported>Ed Poor m (→Automated unit testing: advocates of test-first development) |
Pat Palmer (talk | contribs) m (Text replacement - "[[Ruby programming language|" to "[[Ruby (programming language)|") |
||
(13 intermediate revisions by 7 users not shown) | |||
Line 1: | Line 1: | ||
'''Unit testing''' of computer software ensures that a component of a computer program works as intended. For example, a square root function should return a number which, when multiplied by itself, is sufficiently close to the original argument of the function. | {{subpages}} | ||
'''Unit testing''' of computer [[software]] ensures that a component of a [[computer program]] works as intended. For example, a [[square root]] function should return a number which, when multiplied by itself, is sufficiently close to the original [[argument]] of the [[function]]. | |||
The two most common ways of testing a component are: | The two most common ways of testing a component are: | ||
#Write it first, then check for | #Write it first, then check for [[software bug|bug]]s | ||
#Automated unit testing | #Automated unit testing | ||
Line 11: | Line 13: | ||
The second sort of unit testing seems paradoxical. The programmer writes a suite of automated tests, i.e., a program which verifies the program. The simplest sort of test exercises a routine by feeding it inputs and comparing outputs to expected values. For example, a square root routine should return a result of 5.000000 for an argument of 25. | The second sort of unit testing seems paradoxical. The programmer writes a suite of automated tests, i.e., a program which verifies the program. The simplest sort of test exercises a routine by feeding it inputs and comparing outputs to expected values. For example, a square root routine should return a result of 5.000000 for an argument of 25. | ||
Some advocates of [[test-first development]] even recommend writing a test that won't | Some advocates of [[test-first development]] even recommend writing a test that won't compile, if there is no existing code in place to test. In that case, the first task is to write stub routines so that the test compiles (but fails). | ||
Once a test (or suite of tests) has been written, the programmer then writes just enough programming code to make the tests pass. At this point, he stops because there is nothing left to do. Once all tests pass, there is no "debug-test" cycle. | |||
Advocates such as [[Kent Beck]] and [[Martin Fowler]] consider this a great advantage (see [[Refactoring]]). | |||
==Testing and refactoring== | |||
In test-first development, the credo is "Don't write code until you have a failing test" and "Do the simplest thing that could possibly work." | |||
With a suite of automated unit tests in place, refactoring is transformed from a risky venture into a valuable and risk-free process. The structure of the computer program can be changed radically, with no chance of introducing [[software defect]]s, because after each change the programmers run the automated tests again. As long as all the tests pass, there is no problem. | |||
Usually refactoring is accomplished in a series of small steps as the program evolves towards the new design. | |||
==Unit testing frameworks== | |||
As unit testing has become popular, a variety of unit testing frameworks have been created for those following [[test driven development]]. Most of these follow from the design of [[JUnit]], the unit testing framework for the [[Java programming language]]. Other languages have similar frameworks - [[C Sharp|C#]] has [[NUnit]], [[Python programming language|Python]] has the in-built 'unittest' module, [[Ruby (programming language)|Ruby]] has 'test/unit' and so on. | |||
In addition, some have suggested that [[behavior driven development]] may be a better practice to follow than [[test driven development]], and in order to facilitate this, people have worked on creating frameworks that structure tests by behavior: [[Ruby (programming language)|Ruby's]] [[RSpec]] was the first available, and others have followed for other languages - including [[JBehave]] for [[Java programming language|Java]], [[NSpec]] for [[C Sharp|C#]]/[[.NET Framework]], and [[Shoulda]] for [[Ruby (programming language)|Ruby]]. | |||
A well-written unit test also serves an additional function: it provides documentation for future developers about how functionality works. | |||
For more complex testing additional GUI testing frameworks and mock objects frameworks typically used. | |||
== Example == | |||
The 'xUnit' frameworks uses "assertions", where one takes two values and compares them. Here is an example of some code written in the [[Ruby (programming language)]] that shows how a unit test assertion works: | |||
<pre>require "test/unit" | |||
class TestSquare < Test::Unit::TestCase | |||
def test_square | |||
assert_equal(16, square(4)) | |||
end | |||
end | |||
def square(num) | |||
return num ** 2 | |||
end</pre> | |||
The first line simply requires the presence of the unit testing library 'test/unit'. The class TestSquare inherits from the TestCase class, and defines a test method. Inside that test method, there is an assert method called "assert_equal" which takes two arguments: the first is the expected value, and the second is the actual value. This is a call to the square function, which returns the number to the power of two. If the two match, one will get a result like this: | |||
<pre>Started | |||
. | |||
Finished in 0.000436 seconds. | |||
1 tests, 1 assertions, 0 failures, 0 errors</pre> | |||
If we were to change the number that we expected from 16 to, say, 12, we would get the following result: | |||
<pre>Started | |||
F | |||
Finished in 0.116845 seconds. | |||
1) Failure: | |||
test_square(TestSquare) [(irb):5]: | |||
<12> expected but was | |||
<16>. | |||
1 tests, 1 assertions, 1 failures, 0 errors</pre> | |||
Here, the result tells us that it was expecting to get 12, but got 16 instead, and so one of our assertions failed. |
Revision as of 15:37, 19 July 2024
Unit testing of computer software ensures that a component of a computer program works as intended. For example, a square root function should return a number which, when multiplied by itself, is sufficiently close to the original argument of the function.
The two most common ways of testing a component are:
- Write it first, then check for bugs
- Automated unit testing
Code and fix
The first sort of unit testing, derisively called "code and fix" by prominent author Steve McConnell, has several problems. It takes a lot of time and often does not find all errors. The cost of fixing software bugs rises exponentially during the software development cycle. If the problem is found in the design phase, it's relatively easy to fix. If it's found while coding is going on, but the software has not shipped (i.e., been published), it's harder to fix but still feasible. If the software ships with a defective routine, then at best a new release will have to be created and distributed. At worst, loss of equipment and human life can occur (see Ariadne software bug).
Automated unit testing
The second sort of unit testing seems paradoxical. The programmer writes a suite of automated tests, i.e., a program which verifies the program. The simplest sort of test exercises a routine by feeding it inputs and comparing outputs to expected values. For example, a square root routine should return a result of 5.000000 for an argument of 25.
Some advocates of test-first development even recommend writing a test that won't compile, if there is no existing code in place to test. In that case, the first task is to write stub routines so that the test compiles (but fails).
Once a test (or suite of tests) has been written, the programmer then writes just enough programming code to make the tests pass. At this point, he stops because there is nothing left to do. Once all tests pass, there is no "debug-test" cycle.
Advocates such as Kent Beck and Martin Fowler consider this a great advantage (see Refactoring).
Testing and refactoring
In test-first development, the credo is "Don't write code until you have a failing test" and "Do the simplest thing that could possibly work."
With a suite of automated unit tests in place, refactoring is transformed from a risky venture into a valuable and risk-free process. The structure of the computer program can be changed radically, with no chance of introducing software defects, because after each change the programmers run the automated tests again. As long as all the tests pass, there is no problem.
Usually refactoring is accomplished in a series of small steps as the program evolves towards the new design.
Unit testing frameworks
As unit testing has become popular, a variety of unit testing frameworks have been created for those following test driven development. Most of these follow from the design of JUnit, the unit testing framework for the Java programming language. Other languages have similar frameworks - C# has NUnit, Python has the in-built 'unittest' module, Ruby has 'test/unit' and so on.
In addition, some have suggested that behavior driven development may be a better practice to follow than test driven development, and in order to facilitate this, people have worked on creating frameworks that structure tests by behavior: Ruby's RSpec was the first available, and others have followed for other languages - including JBehave for Java, NSpec for C#/.NET Framework, and Shoulda for Ruby.
A well-written unit test also serves an additional function: it provides documentation for future developers about how functionality works.
For more complex testing additional GUI testing frameworks and mock objects frameworks typically used.
Example
The 'xUnit' frameworks uses "assertions", where one takes two values and compares them. Here is an example of some code written in the Ruby (programming language) that shows how a unit test assertion works:
require "test/unit" class TestSquare < Test::Unit::TestCase def test_square assert_equal(16, square(4)) end end def square(num) return num ** 2 end
The first line simply requires the presence of the unit testing library 'test/unit'. The class TestSquare inherits from the TestCase class, and defines a test method. Inside that test method, there is an assert method called "assert_equal" which takes two arguments: the first is the expected value, and the second is the actual value. This is a call to the square function, which returns the number to the power of two. If the two match, one will get a result like this:
Started . Finished in 0.000436 seconds. 1 tests, 1 assertions, 0 failures, 0 errors
If we were to change the number that we expected from 16 to, say, 12, we would get the following result:
Started F Finished in 0.116845 seconds. 1) Failure: test_square(TestSquare) [(irb):5]: <12> expected but was <16>. 1 tests, 1 assertions, 1 failures, 0 errors
Here, the result tells us that it was expecting to get 12, but got 16 instead, and so one of our assertions failed.