DEV Community

Cover image for TDD, a non-technical intro
Shaquil Maria
Shaquil Maria

Posted on • Edited on

TDD, a non-technical intro

Photo by Christopher Gower on Unsplash

As I am finishing my degree in ICT, I had to do an internship at an ICT company. I was interested in software development, so I decided to apply to a local software solutions company: Ictual N.V. After some talks to get to know the company and their products it was time to decide on an internship assignment. I choose the path of least resistance and decided to work on a web application, a portal for one of their products. Web dev is what I was doing at that time for a year so I was pretty comfortable with the choice of assignment. Then we started discussing their requirements for the assignment and they hit me with this: "This portal should be developed in accordance with the team's workflow, it should be developed according to Test-Driven Development."


My jaw dropped... WTF is that, I've heard about tests before but Test-Driven Development? Never. So That was a challenge for that internship. I learned something new, and I hope that this high-level explanation will shine a light on TDD for you, if like me you've never heard of the term.

Test Driven Development, what is it exactly?

Well, TDD is a software development approach where you write test cases that explains your code before the actual code. For example, if you want to create a calculator, you will have to write tests for each functionality of the calculator (summation, subtraction, multiplication, division, etc.). After writing those tests then you will be writing the minimal code to pass those tests. In the case of summation, you will write the code, to sum up two numbers.

Test-Driven Development can be done in five basic steps:

  • Write a test.
  • Run the test and see it fails.
  • Write the simplest code to satisfy the expectation of the test.
  • Run the test again and see if the written code passes the test. If it passes refactor the code and if not change the code.
  • Repeat

When talking about TDD, we can talk about two different levels of tests:

  • Acceptance TDD (ATDD) - you write a single acceptance test that fulfills the behavioral specification of your project. Behavioral specifications describe how the different classes and modules in your application with each other. This is also known as Behavior Driven Development. The aim of ATDD is to specify the requirements of your project. Most times this is achieved by working with User Stories and Use Cases.
  • Developer TDD (DTDD) - you write a single test that focuses on every small functionality of your project. DTDD is simply called TDD.

Now that we have an idea of what TDD is, the next logical question is: WTF are tests anyways?

Tests

For every time that the word "test" was mentioned, let's take a shot of... water 🥤, we should be sober for the next part.

Tests are assertions that you write about the functionality of your software, "It should sum up two numbers". In a test, you describe the functionality and what you expect from it when it is executed. For example:
description: "The calculator should sum up two numbers", expectation: "2 + 2 = 4". That just now was a Unit Test. Unit tests allow you to set descriptions and expectations of one specific logic in your code.

The general rule of thumb is that for every Unit Test, you write a function. This simplifies your code and makes it easier to debug and reuse in the future.

In software development, there are a ton of different forms of software testing, but the most important tests you will be writing as a developer are Unit Tests and Acceptance tests. For you, front-end developers, end-to-end (e2e) testing will be pretty important. In e2e you will be testing the UX of your application. How all the components flow and work with each other.

Important best practices when creating tests

  • Use descriptive names for test functions, by the name of the function you should know what you are testing.
  • Separate testing code from implementation code but keep testing classes in the same directory as the implementation code.
  • Limit the number of assertions in a test, write more test functions if necessary. If you need to test multiple assertions (expectations that the code really executes what it is intended to execute) it is recommended to create a test for each one of them. This makes debugging easier and the testing code cleaner to read.
  • Write the simplest code that would pass the test. Separate your logic into different and write the simples code to pass each test at a time. At the end of the test suite, your code will be executing the complex logic because each individual section works as it should.

Now, let's write a test for our summation example. I will write this in the format of the tool Jasmine. Jasmine is one of many testing tools developed for the language JavaScript. Jasmine comes preinstalled in your Angular project, which is what I used for my internship. While depending on your programming language and tool the syntax may vary, the general gist of how to write a test is the same.

// import dependencies for testing
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ComponentName } from "./component-name.component";
describe("CalculatorComponent", () => {
  // create component instance
  let component: ComponentName;
  let fixture: ComponentFixture<ComponentName>;
  beforeEach(async () => {
    // import and declare dependencies for the component
    await TestBed.configureTestingModule({
      declarations: [ComponentName],
    }).compileComponents();
  });
  beforeEach(() => {
    // initialize component instance
    fixture = TestBed.createComponent(ComponentName);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
  // test that should be run on the component code
  it("should calculate a sum", () => {
      const expectation = 4;

    expect(component.sumNumber(2,2)).toEqual(expectation);
  });
});
Enter fullscreen mode Exit fullscreen mode

Again, the code above is specifically for Angular. Your mileage may vary but that right there is a working example of a test to assert that the function called sumNumber returns the sum of 2 + 2.

You will first import the tools you need to test your code and the piece of code you want to test. Then you start writing your test suite. Your test suite is all the tests you will execute for a specific part of your application (software 🤷🏿‍♀️). Your test suite lives in a describe function. The first parameter will describe the part of your application that you are testing, the second part will describe the function that will contain your tests.

Then you write the beforeEach. In your beforeEach function, you write what should happen, well, before each test case. Here you create a new instance of your piece of code and pull in every dependency for your tests.

After that comes the fun part, the tests. The tests have an almost human language type of syntax. All your tests will be in the form of an it function. The first parameter will be the description of your test, what exactly are you testing? What should it (your code) do? The second parameter contains the function that will execute your code and run it against your expectation. In that function, you need to set all that is necessary for the logic to function properly. In our summation case, call the function with the two numbers we want to sum.

Remember, the first time you are running your tests, they should fail. The test should be written before the code.

With your tests in place, go on and start developing the best application you can. Using Test-Driven Development will encourage you to think critically about your code and allow you to write cleaner code. The benefits of cleaner code are endless. Your code will be easier to understand, easier to reuse, easier to add new developers to the project, and easier to debug among other things.

I hope that you learned something new from this post and that you will consider using this development approach for your next million-dollar application idea.🤙🏿

For the ones that use tests regularly or even TDD. What does a test look like in your programming language of choice?

Top comments (10)

Collapse
 
jayjeckel profile image
Jay Jeckel

I still don't get the Why of TDD. If I have my specification for the software, what practical difference does it make if I write the tests before the functionality or the functionality before the tests? Either way, both pieces of code will be the exact same.

Collapse
 
theowlsden profile image
Shaquil Maria

The reason to write the tests before you start with the code is to separate the concerns of the specifications from the get-go and to start with a clean slate that will allow you to write the minimum code to satisfy the specification.

In my opinion, if you write the test before the code, you can clarify the specification better (as a developer) and you start with an unbiased test. By unbiased test, I mean that your test is the point of truth and not your code. If you start by writing your code, you tend to write a test that your current code will pass, instead of a test purely based on the software specification.

Either way, both pieces of code will be the exact same.

That could be the case indeed. I think that if you have the tests ready before you start coding you can adapt and refactor your code easier than writing the code, then the test, and then refactor.

I hope I made the Why of TDD a bit more clear for you.

Collapse
 
jayjeckel profile image
Jay Jeckel

Thank you, that at least clarified for me where the disconnect between our perspectives is: I don't see the tests as the point of truth, the specification and design documents are the points of truth that all code, test or otherwise, should follow.

Thread Thread
 
theowlsden profile image
Shaquil Maria

the specification and design documents are the points of truth that all code, test or otherwise, should follow.

You are totally right there and I agree with that. But what I mean is that the tests clarify that truth. For some, it is important to have the specification separated into bits and pieces that make it easier to tackle, for others they dive headfirst and hope for the best.

Tests are nothing without the specification, you need to know what you are building.

What is your point of view on TDD?

Thread Thread
 
jayjeckel profile image
Jay Jeckel

I don't know if I have a view on it as I don't think I really get the purpose of it. I've done it for a few projects and didn't see any inherent benefit from writing tests before the methods.

If I have a requirement to add two numbers, return zero if the first number is zero, and throw an error if the second number is less than zero, then I already know what code will be in the method and what functionality needs to be tested.

I could give those requirements to two different devs and have one write the test and the other write the implementation, give to one dev and have them write the test first and method second, or one dev writing the method first and the test second; in any of those cases the outcome is going to be the same, a test and a method that meet the specs.

Maybe it is a disconnect at the idea of what a specification is, but if you are having to refactor the method and the test a bunch, then it sounds like not enough prototyping was done in the first place to make a properly detailed spec.

If I have any point of view on TDD, it's that writing tests first seems like cargo cult fluff that overshadows the actual important message, that tests are vital and should be a fundamental part of development, regardless of them being written first, second, or last.

Thread Thread
 
theowlsden profile image
Shaquil Maria

that tests are vital and should be a fundamental part of development, regardless of them being written first, second, or last.

At least we can all agree on that part.😂 If you write it first or last, it is up to you and your team, but testing your code is important.

Maybe it is a disconnect at the idea of what a specification is, but if you are having to refactor the method and the test a bunch, then it sounds like not enough prototyping was done in the first place to make a properly detailed spec.

Can you elaborate a bit more on that part? If I understand you correctly, the specifications you are working with are super specific, almost like the tests you would be writing (thus, setting the assertion and expectations).🤔 I might be wrong there.

Collapse
 
theowlsden profile image
Shaquil Maria

PS: @ederchrono wrote a great post explaining how TDD is used in a real-world scenario. Check it out!

Collapse
 
kreely profile image
Dave Creelman • Edited

TDD, so useful. Kind of a backbone to CI/CD. Thanks for this.
In Delphi, tests can be done using DUnit (based I think on JUnit from back in the day).
Run all the tests before each build of a product (run them automatically each night if you can).
Designing with testing in mind changes the whole architecture of a solution in a good way.

Collapse
 
theowlsden profile image
Shaquil Maria

TDD, so useful. Kind of a backbone to CI/CD. Thanks for this.

Yes! That's the end goal, create tests that would run automatically before you build for production.

In Delphi, tests can be done using DUnit

Alright, good to know🤔 I think for most language the primary tool is the name+Unit😅😂

Designing with testing in mind changes the whole architecture of a solution in a good way.

Preach!

Collapse
 
theowlsden profile image
Shaquil Maria

Glad to know it was easy to follow! Thank you for reading!😁