How to ensure functionality is tested consistently?

As an example, lets say I am working on creating an eCommerce solution. Part of the functionality of this solution will be to ensure that the final price of an order is correct. Lets say the final price will include the grand total of all products, the shipping costs and any tax.

Lets say I have two public methods:

/**  * @return array  */ public function viewAllOrders(){};  /**  * @return array  */ public function viewOrder($guid){}; 

Each method returns (amongst other things) the grand total of an order.

The problem I have run into is that because each of these methods is tested in separate tests, the tests could have different definitions on what the grand total should be.

For example, lets say for an order 'A' the product price is $100, shipping $10, tax $20 giving grand total $130.

A developer is writing the test for viewAllOrders. He writes the test correctly, and asserts that for order 'A' the grand total is $130.

Another developer writes the test for viewOrder. He thinks that grand total is just the price for the products, and forgets about tax and shipping. He now asserts that for order 'A' the grand total is $100.

Both developers write production code that ensures all the tests pass, but we now have a situation where order 'A' has a different grand total depending on the public method that you call! Errors have crept into our system (even though all tests are passing) because two tests disagree on the definition of grand total.

What are some good ways to ensure that we don't use different definitions of functionality across our tests?



I am adopting 'Classical/Statist' testing over mocking. The reason being is I'm using a dynamic language. See Are mocks in dynamic languages dangerous?. This means I cant just simply have a grandTotal() method that the other classes call and can then be mocked in tests.

I have also considered testing each method in the same test (thus ensuring the definition of grand total stays in one test). The problem with this is that you open yourself up to Assertion Roulette.

Also note I have trivialise this example. Most people do know what grand total is, but my problem is a general one, being explained with a simplified example. When you read 'grand total' this could equally be read as 'some value with definite meaning'

Replay

You ensure that by properly knowing your domain and designing for it.

In some other software, the calculation of a+b may be almost irrelevant to the domain. In your case, you are talking about the commerce domain, in which tax and shipping are vital things. Therefore, if a method simply does return cost + tax + shipping you have a horrible design flaw.

This is similar to the emphasis on context mentioned by the other answer, but I find domain more understandable. Why is it important to properly model your domain? Because your software is built around and for it.

Let's make this more concrete: Assume that you somehow managed to ensure that the two methods perform the calculations equally, but separately.

  • What will you do once you discover, that the tax rate is different for each country?
  • How do you ensure the correct price calculation for super-important customer X who gets a 3% discount on everything they order?
  • How do you adjust the calculations for next week's free shipping promotion?

If you're in the business of selling stuff, then price calculation is of such vital importance that having two (let alone more than that) places in your code for deciding it becomes a major liability.

Now, should you discover such a violation, then you do not want to simply fix the two calculations to be the same. You go to the underlying root cause of the problem and re-design your system.

You may want to check out the domain-driven design (DDD) literature on this. Most of that is going to help you avoid such problems in a more general way (bounded contexts and ubiquituous language come to mind immediately).

This sounds like a semantic argument.

One says:

order 'A' the product price is $100, shipping $10, tax $20 giving total price $130.

The other says:

for order 'A' the total price is $100.

The difference? Context.

You don't have a situation where order A has a different total price. You have a situation where any order has both a sub-total and a grand total that includes things besides order A (shipping, taxes). If you call both these things total and expect them to be the same it's as silly as if you'd switched to order B and expected things to be the same.

What are some good ways to ensure that we don't use different definitions of functionality across our tests?

Use good names. Make the context clear. A test that only the author of the test understands is a bad test. Total just means some stuff has been added up. It's not a very useful name. Give me a hint about what's been totalled up. Subtotal tells me this is just the first wave of totalling. Grandtotal tells me this is the last of the totalling.

To help ensure that code performs as expected and is understandable you can have peer reviews and have peers write their own tests against it. A peer test shows how well the code is understood in a way so formal that it compiles.

  • Flesh out a language shared between all developers and business experts (see Ubiquitous Language in Domain Driven Design). Model your domain concepts precisely and unambiguously around that language. The authority for price calculation should be in one central place.
  • Since you seem to use an Outside-in TDD approach, when you approach the core layer (the domain), you'll already have the names of the domain objects thanks to that model. The first developer to need a given domain object (or part of it) can implement it. Team members who subsequently need it will use that implementation.

(edit)

This might differ from an extreme "TDD as if you meant it" approach, as I don't recommend refactoring your domain objects out of the production code as you go. Instead, I believe that when you hit the edge of the domain, you should pause your current test and go and create another unit test for the domain object you need. Implement it completely before going back to the outer layer test. Unlike more technical code, modelling rich business concepts and rules cannot be done in the heat of a minutes-long refactor step. They are too important to be improvised.

As a side note, I guess that a more extremist TDD practice tends to imply pair programming all along and rotating pairs frequently, thus generating much fewer opportunities for conflicting/duplicate implementations. Which might work just as well.

Category: testing Time: 2016-07-29 Views: 1
Tags: tdd testing

Related post

iOS development

Android development

Python development

JAVA development

Development language

PHP development

Ruby development

search

Front-end development

Database

development tools

Open Platform

Javascript development

.NET development

cloud computing

server

Copyright (C) avrocks.com, All Rights Reserved.

processed in 0.187 (s). 13 q(s)