Introduction

This article deals with an idea that I believe to be one of the key concepts of modern programming. When I say modern, I'm referencing to methodologies that re-appeared with hype names and excessive power during the second half of the 90's and the beginning of the 21st century as an answer to the bureaucratic, slow and heavy regulated methods in use at the time. As some of their examples are: ASD(Adaptive Software Development), DSDM (Dynamic Systems Development Method), Scrum, XP(Extreme Programming) among others...

These methodologies, that were known as lightweight methods until year 2001 and then became the Agile Software Development, have some aspects in common. At first, they were "born" from the developers necessity to focus more on the product in which they're working on than in hard processes related to development. It means that, unlike the classic long-term strategies and specifications that predict and document the entire project from the beginning to the end, the development is made in small iterations, with minimal planning. Another important factor is that these methodologies are in favour of face-to-face communication rather than written documentation among the team members (which are small, around 5-9 people). Third and most important to all programmers, in my humble opinion, is the constant execution of automated tests in all steps to ensure the quality of each small unit of code they generate.

Despite being an interesting topic, I'll stop my wondering here, because it's not my goal to teach techniques to manage software teams, but to teach one of the things that all of those techniques will be require you to do. Before anything, I want to remind you that a programmer will always program, so that doesn't matter too much what methodology is adopted by the company where you are working on. For fun, below is a strip from Geek Hero Comic that was kindly provided by the author Salvatore Iovene for this article:

Agile Development Explained from Geek Hero Comic

For those who don't know yet, this is the third article of a series that I committed myself to write. Here is the list of articles:

  1. ASP, a misinterpreted technology
  2. Event-Driven-Programming and lambda function in ASP/VBScript.
  3. TDD (Test Driven Development) in ASP/VBScript.
  4. Languages: Based on Objects and Object Oriented.
  5. Object Oriented in ASP/VBScript "Hackers way".
  6. "Scripting Components", the ace in the role.
  7. Caching: the concept of DRY (Don't Repeat Yourself) applied to ASP.

If you're reading one of my articles for the first time, I strongly recommend that you read the previous ones first, because I'm trying to lead you into a great abstraction, presenting the topics incrementally.

Test Driven Development

First of all, TDD is not just a simple method to test the code, but rather a philosophy to develop it. Why is it important? Well, to get an idea, like this NIST (National Institute of Standards and Technology) points, an estimated 59.5 billion dollars or 0.6% of the gross domestic product are lost due to software bugs. The article says many interesting things, for example: "more than a third of these costs, or an estimated $22.2 billion, could be eliminated by an improved testing infrastructure that enables earlier and more effective identification and removal of software defects", "Currently, over half of all errors are not found until "downstream" in the development process or during post-sale software use.", but wait, don't be horrified, not everything is programmers fault: "Other factors contributing to quality problems include marketing strategies, limited liability by software vendors, and decreasing returns on testing and debugging", "The increasing complexity of software, along with a decreasing average product life expectancy, has increased the economic costs of errors" and so on... - If you have time, I suggest reading the entire article.

Ok, given the motivation (it's always good to be motivated to learn something new), let's go to the algorithm of the methodology (taken from the book "Test Driven Development: By Example" - Kent Beck) with some additional comments:

  1. Add a test
    In TDD, each new feature begins with the writing of a test. To write a test, the developer must clearly understand the specifications and requirements of the features. It can be achieved through use cases and user reports that covers the requirements and exceptions. This can also generate variations or modifications of existing tests. Anyway, this is a differential of TDD - the developer should focus on the requirements before starting to code, a subtle but important difference.

  2. Run all tests and fail
    This step validates that the test was well consolidated and the new test doesn't pass the old code without any changes. It's important that the written test fails. Otherwise, the test doesn't have any value, or the feature is already there.

  3. Make a change
    Here, you must code an algorithm that passes the test. The code written in this step won't be perfect and may, for example, pass the test in a tacky way. This is still accepted in this step, since the subsequent process steps will have to improve and refine the code.

  4. Run all tests and pass
    If all tests pass now, the programmer can be sure that the code meets all the requirements. It's a good place to begin the last step of the algorithm.

  5. Rewriting the code
    Now the code can be improved as necessary. Re-running all tests, the programmer is sure that the rewriting is not affecting any existing functionality. The concept of removing duplication is important in any software design.

That's it, now you know all the steps of this methodology. To implement a new functionality, always begin with the step number 1. It's highly recommended that the amount of changes between the tests never be large, so the programmer can simply use the undo instead of debug the program to find the error.

Using the knowledge

Suppose that you need to implement a new method in a program. The method is called factorial and receives a natural number as argument. It's function is to be equivalent to the n! operation of mathematics, except it should return (-1) when unable to perform the task. Learn more about the factorial operation.

So let's begin creating our test:

<!--#include file="libs/base.asp"-->
<!--#include file="libs/stringbuilder.class.asp"-->
<!--#include file="libs/unittest.class.asp"-->
<code><pre>
<%

dim Tester : set Tester = new UnitTest

Response.write "Testing" & vbNewline
Response.write "=======" & vbNewline
Response.write Tester.test("factorial", array(-1), -1) & vbNewline
Response.write Tester.test("factorial", array(0), 1) & vbNewline
Response.write Tester.test("factorial", array(1), 1) & vbNewline
Response.write Tester.test("factorial", array(2), 2) & vbNewline
Response.write Tester.test("factorial", array(3), 6) & vbNewline
Response.write Tester.test("factorial", array("string"), -1) & vbNewline
Response.write Tester.test("factorial", array(2.718281828), -1) & vbNewline

Response.write vbNewline

set Tester = nothing

%>
</pre></code>

This test should return:

Testing
=======
Failure: factorial(-1) != (integer)-1. Method returns: (empty)
Failure: factorial(0) != (integer)1. Method returns: (empty)
Failure: factorial(1) != (integer)1. Method returns: (empty)
Failure: factorial(2) != (integer)2. Method returns: (empty)
Failure: factorial(3) != (integer)6. Method returns: (empty)
Failure: factorial("string") != (integer)-1. Method returns: (empty)
Failure: factorial(2.718281828) != (integer)-1. Method returns: (empty)

Which was exactly what we were expecting. All tests failed because this function doesn't exist yet! As everything went wrong, the step 2 was a success, let's keep going to step 3 and include the following function:

function factorial(n)
    if( lcase(typename(n)) = "integer" ) then
        if( n >= 0 ) then
            factorial = 1
            dim i : for i = n to 2 step (-1)
                factorial = factorial * i
            next
        else
            factorial = (-1)
        end if
    else
        factorial = (-1)
    end if
end function

Run the test again and receive:

Testing
=======
Success: factorial(-1) == (integer)-1
Success: factorial(0) == (integer)1
Success: factorial(1) == (integer)1
Success: factorial(2) == (integer)2
Success: factorial(3) == (integer)6
Success: factorial("string") == (integer)-1
Success: factorial(2.718281828) == (integer)-1

Yes! The function, at least, works. Now it's up to you to improve it and re-test to ensure it still works as desired. Take a look at the final version:

function factorial(n)
    if(vartype(n) <> 2) then factorial = (-1) : exit function
    if(n < 0) then factorial = (-1) : exit function
    if(n = 0) then factorial = 1 : exit function
    factorial = n * factorial(n - 1)
end function

The above code shows a very particular and interesting feature of VBScript. It can't be written as:

if( ( vartype(n) <> 2 ) and ( n < 0 ) ) then factorial = (-1) : exit function

As we're used to in other languages, because VBScript doesn't work with short-circuiting logical operations (despite VB has elseor and andalso) that are instructions where the interpreter can skip the analysis of the second expression depending on the outcome of the first one. This means that if we put both conditions together in the same if, the compiler analyses it as a single expression and immediately prints the error type mismatch for non-integer. Therefore, when asked if the data is integer with on error resume next, it answer yes, even when the content is string.

Note: The error above doesn't occur due to the fact that VBScript is loose typed, other languages such Javascript and Python interpret the expressions together correctly, as we're used to. This is truly one of the VBScript failures.

Download

As I used in the examples some of the AXE(ASP Xtreme Evolution) includes, I prepared this zip file containing the final test and the files needed to run it. By the way, this is one of the major features of the AXE Framework: many of it's components were built in a way that doesn't require the complete platform to work. So, if you need any component that the AXE offers, for example: upload, just copy the files, their dependencies, and you will be fine. Everything runs perfectly!

Conclusion

Like the Toyotism in the automobile production history, which preached among other things:

  1. Flexible mechanization as opposed to the rigid Fordist automation,
  2. Total quality, which implements quality controls for all employees in all parts of the production process as opposed to the quality assured by sampling,
  3. Customized products
  4. Just-in-time, which aims to "produce the required, in the necessary amount and only when necessary"

and revolutionized the process of building, I belive that the TDD is one of the most important methods of the software development, because it reduces considerably the errors during development. If we add to it the small intervals between iterations of the Agile Software Development techniques, we're able to customize the best product on-demand, i.e. just in time (as soon the new specifications of the problem appear), thereby ensuring a higher quality to the final product. For those more interested, I recommend another reading: On the Effectiveness of Test-first Approach to Programming.