XP and Automated Tests

XP and Automated Tests

Whenever I read one book and I read next, I tend to draw parallels or look out for some analogy to understand better. And when I was revisiting the book by Kent Beck on Extreme programming (XP) , I ended up drawing analogy with daily things I do! XP is based on core five values, and I was astonished to see how each of these values correlates with the Automated Test practice. Let's see what each XP value has to say about tests!

Communication

  • Communication is fundamental tool for any kind of activity. We all are aware that communication matters the most in XP, everything needs to communicate meanings, intention or impact! Be it from story, standup updates and code is no exception to that!
  • It is true that developers spend their time in reading code more than writing code and the quickest way to understand the code is reading tests! If a developer has to spend time puzzling around a test to figure out what it means and what it does, which means they have less time to spend on the features, fixing bugs and thus drop in velocity.
  • Test names convey either a rule or a behaviour to the newly joined member or the existing members. Hence they need to be chosen with care. Tests should be clean, and what's the rule for tests being clean? Readability, Readability and Readability!
  • The name of the test should be the first clue for a developer to understand what is being tested and how the target object is supposed to behave. The name should be descriptive and should define the behaviour, the what rather than the how.
`should trigger mail alert job` --> does not define the clear behaviour of when it should and when it should not
`should trigger a mail alert job when time reaches threshold` --> defines clear intent.
  • The way we arrange the test code also improves readability. Arrange, Act, Assert or Setup, Execute, Verify, Teardown. These guidelines help to make code more readable by dividing code structure in such a way that it's easier to know what is happening where. There are some dsl provided in some languages as framework (Given, When, Then), which are created with the sole purpose of bringing a skeleton for the tests.
 @Test
    void shouldReturnInterestBasisForLeapYear() {
        var clock = Clock.fixed(Instant.parse("2000-12-03T10:15:30.00Z"), UTC);
        var basisProvider = new LeapYearInterestBasisProvider(clock);
        //1
        var currentInterestBasis = basisProvider.getCurrentInterestBasis();
        //2
        assertThat(currentInterestBasis).isEqualTo(366);
    }

//1,2: Space helps to structure the code and read 
//code better at a glance.

Simplicity

  • Kent Beck mentions that Simplicity is one of the intellectual of XP values. We do tend to apply this when deciding scope, and we say, what's the simplest thing we can do right now that would possibly work? And I have seen decisions changing drastically when this was challenged in team!
  • Test code need to be simple to achieve readability. There are certain principles out there which in a way makes tests to be cleaner and thus simpler.
  • Test name should be simple and should communicate business domain with clear intent.
  • Sometimes test data setup makes test bulkier. It is good practice to extract test helpers to reduce duplication, bring discipline across test suite and thus increase readability, in turn reading and updating test simpler.
  • Sometimes test helpers are not useful. We often see FakeServer, AcceptanceTestDriver which sets up the common structure needed for the test code. Another technique when test data setup gets bulkier is to use test data builders. For a class that requires complex setup, we create a test data builder that has a field for each constructor parameter, initialised to a safe value. The builder has chain-able public methods for overwriting the values and the build method, returns new instance.
data class CountryCode(val code: String, val country: String)
data class PhoneNumber(val countryCode: CountryCode, val number: String)

data class Address(val streetCode: Int, val block: Int, val unit: Int, val pin: String)


data class Person(val name:String, val age: Int, val address: Address, val phoneNumber: PhoneNumber)

You can imagine creating test data for Person class, repeating it in every test wherever needed and finding creative names for those. With builders, it becomes easier to hide this complexity and repetition and thus making tests more simple and still readable.

class PersonBuilder(
    val name =  aRandomName(), 
    val age =  aRandomNumber(), 
    val address = anAddress(), 
    val phoneNumber = aPhoneNumber(),
){
    fun build(): Person = {
     Person(name, age, address, phoneNumber)   
    }
}

Val person = Person(name = “Alice”, age = 30) /
/* This will create a person class with name as Alice and age 
as 30 and all other data is created by builder under the hood.
This gives you flexibility to define in test with respect to 
context of the test and thus override only those parameters.   */
  • I was of the opinion some time back that everything which is input should be mocked, but I was wrong. We should not mock value object (if setup is complex, create test data builders), we should only mock entities whose behaviour needs to be controlled. There are some layers which we can not mock, example, external API, we would not be able to mock these as we can not own them! What to do, write an adapter layer for those. In a way just make your test data simple by following discipline.

Feedback

  • We apply feedback at every stage of XP. We do retrospective to reflect back and look upon where we could improve as no fixed direction can be valid for long. We incorporate feedback from this and make changes accordingly. Feedback help us to reflect and act upon. Quicker feedback, it is better and hence we strive to have continuous deployments.
  • Tests give us feedback instantly whether our code is working or not. We run tests, and voila, it turns red or green, indicating feedback to us.
  • The smallest unit of code fails and we get the feedback early on time and thus can act upon it.
  • There are tests which give different feedback. Unit tests help to drive our code, Integration tests make sure that integration with components works correctly, contract tests help to validate the communication boundaries between different systems. infra tests help to make sure the configurations are correct.
image.png
  • Test pyramid has been there for a reason. It talks about no of tests vs time it takes to run each test. Also the cost of setting up tests at a higher level is more than setting up structure in place for the unit tests.
    Team needs to choose which tests they want to have, and it might evolve later. The intent of adding any testing layer has to be clearly defined and the feedback test will bring, would be of importance.
  • Given the major purpose of adding the tests is for quicker feedback, the tests failure should point to correct failure. Provided tests should be simpler, the failure should be easier to understand as well. Hence it's worth spending time on correct assertions and failure messages.

Courage

  • Tests bring different abilities to the project, ability to add code, fix possible bugs. Without proper test coverage, developers might not have Courage in making any change in the code, and also might produce some bugs alongside.
  • If there is some bug fix or feature that needs to be updated, the code has already become a big ball of mud which no one wants to touch! At this point, even though the team wants to refactor, they fall into a vicious circle of not being able to refactor as there is fear of updating code or test design.
  • If a team gets a legacy code without any test suites, and has to work on the code, they can start by adding tests around existing code. ( Characterization Test )
  • image.png
  • And hence refactoring tests should be a continuous process unlike refactoring of the code. The moment we delay it, it might look like we are going faster for the time being, but later time spent refactoring is much higher and thus the effort.If you let the tests rot, then your code will rot too. Keep your tests clean.

image.png

Respect

  • Respect of team member is crucial in terms of success of project. As at the end, people who develop the software with proper communication and agreed decisions.
  • We do respect the code in production, but at the same time its very important to respect the test code. Sometimes, it's a tendency to write tests in a “quick and dirty” way, and not putting an effort to write in a way one would write a production code! The decision to write tests in a chaotic way is just the start of the failure! Soon, all testing code rattles, and testing efforts go useless without saying!
  • Teams need to Respect tests the similar way, they respect production code. If there is a decision from the beginning to keep the testing code clean, it makes it a habit and thus effort to add any test becomes simpler.
  • Along with keeping tests clean, it is also important that the tests are deterministic. It passes on local, fails on a particular agent, a certain test fails when run in parallel, test takes X time today to run and next day it ends up taking longer, it fails on weekend and so on. If they fail, they fail for the correct reason and fail repeatedly and not with some varying parameter for machine, time or dependency. It is very important to address these tests before the team loses trust on these tests. There are ways to tackle these flaky tests, and you can find that about here .
  • In short, start treating your test code as production code. Tests are not second class citizens. They require the same thought, design and care as production code, as at the end tests signal production deployment.

XP and Automated tests

  • XP's values create the baseline to be followed for a project. these are practices set to have a disciplines approach, and similarly test help team members to learn about the domain, they are talking artifacts! If tests are written in a timely manner, they do give feedback if there is anything broken, and gives courage to refactor and thus make code maintainable and readable.
image.png

Thanks for follow up from making me write, all those reminders to publish Lavanya Mohan. Thao Dang your reviews helped me a lot to structure it better!

Did you find this article valuable?

Support Priti Biyani by becoming a sponsor. Any amount is appreciated!