Book Image

Testing with F#

By : Mikael Lundin
Book Image

Testing with F#

By: Mikael Lundin

Overview of this book

Table of Contents (17 chapters)
Testing with F#
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Building trust


What you see as a developer when you look at legacy code is distrust. You can't believe that this code is performing its duty properly. It seems easier to rewrite the whole thing than to make changes to the existing code base.

The most common type of bug comes from side effects the developer didn't anticipate. The risks for these are high when making changes in a code base that the developer doesn't know. Testers have a habit of focusing their testing on the feature that has been changed, without taking into account that no change is done in isolation and each change has the potential to affect any other feature. Most systems today have a big ball of spaghetti behind the screen where everything is connected to everything else.

Once, I was consulting for a client that needed an urgent change in a Windows service. The client was adding online payment to one of their services and wanted to make sure customers were actually paying and not just skipping out on the payment step.

This was verified by a Windows service, querying the payment partner about whether the order had been paid. I was going to add some logic to send out an invoice if the online payment hadn't gone through.

The following is the invoice code:

// get all orders
OrderDatabase.getAllUnpaid() 
|> Seq.map(fun order ->

    // for each order
    let mutable returnOrder = order
    let mutable orderStatus = OrderService.NotSet

    try
        // while status not found
        while orderStatus = NotSet do
            // try get order status
            orderStatus <- OrderService.getOrderStatus order.Number

            // set result depending on order status
            returnOrder <- 
                match orderStatus with
                // paid or overpaid get correct status
                | Paid | OverPaid -> { order with IsPaid = true }
                // unpaid
                | Unpaid | PartlyPaid -> { order with IsPaid = false; SendInvoice = true }
                // unknown status, try again later
                | _ -> returnOrder
    with
        | _ -> printf "Unknown error"

    returnOrder)

// update database with payment status
|> Seq.iter (OrderDatabase.update)

It was implemented and deployed to a test environment in which the logic was verified by a tester and then deployed to production, where it caused €50,000 in lost revenue.

With my failure, I was assuming the OrderService.getOrderStatus parameter really worked, when in reality, it failed four out of five times. The way the service was built, it would just pick up those failed transactions again until it succeeded.

My addition to the code didn't take the side effect into account and started to mark most failed payments with the status of Paid even though they were not.

The code worked fine while debugging, so I assumed it was working. The code also worked fine while testing, so the tester also assumed it was working. Yet, It was still not enough to stop a crucial bug-hit production.

Tip

Downloading the example code

You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

Bad code is that which is poorly written and does not follow best practices by swallowing exceptions and letting the program continue to execute in a faulty state. This makes the code harder to change, and the risk becomes higher as a change could introduce new bugs.

Tests written for a program will guarantee that the code has better structure and is easier to change. This is because tests themselves require well-structured code in order to be written. Unit tests drive code to become better designed with higher quality and easier to read and understand.

Integration tests will verify that code written to integrate with external systems is well-written with all the quirks the external system needs, and regression tests will verify that the intended functionality of a system be kept even after a change has been introduced.

Building trust with programmers is all about showing robustness, and this is done by tests. They lengthen the lifetime of a system, as those systems are open to change. They also shine through to the end user, as those systems will not crash or hang when the unexpected occurs.