Ad/iklan :

3gp Mp4 HD
Play/Download
Live 3D App
Search.Pencarian Menu

Add text send email to rh3252705.adda@blogger.com or Click this (Text porn Will delete) | Tambah teks kirim email ke rh3252705.adda@blogger.com atau Klik ini (Teks porno akan dihapus)
Total post.pos : 15909+

[Go Make Things] TDD for JavaScript with buildless Mocha and Chai

Test-Driven Development (or TDD) is an approach to coding where you write your tests first, then write the code that gets them to pass.

For years, I've said that while I see the value of TDD, it doesn't align with how my brain works. Turns out, I was just doing it wrong!

Today, we're going to look at TDD the right way. Let's dig in!

This article is part of an ongoing series of JavaScript testing. In past articles, we looked at buildless testing, an overview of testing approaches, how to write unit tests, and how to organize tests.

What is Test-Driven Development, exactly?

With Test-Driven Development, or TDD, you write your tests first.

Because you haven't written any code, the tests should fail. If they doesn't, the tests aren't actually testing anything useful.

Then, you write code until you get all of the tests to pass. Once they do, you can go back and refactor the code to make it easier to read, maintain, and so on. Your tests should continue to pass, unless you messed something up.

This process is often referred to in the TDD community as Red => Green => Refactor.

The lightbulb moment

One of the biggest reasons I shunned TDD for so long—an argument I hear from many other people as well—is that I don't exactly know how much script is going to work until I write it and start exploring with code. How can I test something I don't know yet?

Then I watched this amazing talk from Ian Cooper on TDD misconceptions, and a lightbulb went off.

JavaScript tests shouldn't be testing how your code works. They should be testing what it does. Behaviors, not implementation details.

For nearly a decade, I'd written tests that tested implementation details. Or more often, didn't write tests at all, because every time you change how your code works, the tests break.

In TDD, that shouldn't happen!

You only test the outputs and behaviors, not how the code gets there. As long as the output changes, you can refactor the underlying code as many times as you want without touching the test.

Why is TDD good?

Like many developers, my approach to testing over the years has mostly been code first, test last (or not at all).

The idea with testing last is that you wait until you know exactly how your code is going to work, and get it working. Then you write some passing tests. In the future, if a refactor breaks the code, the tests will catch it.

But generally, tests written this way break everytime you refactor, even if the code is still doing what it's supposed to, because it's tied to heavily to how the code works and not what it does.

After playing around with it for a bit, here's where TDD really wins for me…

  1. It forces you to get really clear on the behavior of the code early, which helps direct how it should work.
  2. I end up writing fewer tests because I'm only testing the external interfaces of my code.
  3. You know the tests are doing what they should, because they fail until your code achieves the stated behavior.
  4. They don't break when I refactor (and if they do, I did something wrong).

With that out of the way…

Let's write some tests with TDD!

We want to build a calculator.js library that provides functions developers can use to add() subtract(), multiply(), and divide() some numbers.

let sales = calculator.add(4, 6, 8);  let total = calculator.subtract(total, 3);  

Before we write any code, we're going to write some tests to define the behaviors we expect from our library methods.

(If you've never done this before, go read my article on how to write unit tests first.)

Inside my test.calculator.js file, I'll first describe() my tests for the add() method…

describe('the add() method', function () {  	// Tests will go here...  });  

Here's the behaviors I'd expect from this method…

  1. When I pass two or more numbers into it, the sum total of them should be returned.
  2. When I pass in just one number, that number should be returned as-is.
  3. When I pass in no numbers at all, 0 should be returned.

And here's how I would express that as a set of tests…

describe('the add() method', function () {    	it('Should add two or more numbers together', function () {  		expect(calculator.add(4, 6, 8)).to.equal(18);  	});    	it('Should return the original value when one number is provided', function () {  		expect(calculator.add(42)).to.equal(42);  	});    	it('Should return 0 if no numbers are provided', function () {  		expect(calculator.add()).to.equal(0);  	});    });  

If I run my test file at this point, nothing will show up, because the add() function is not defined. Let's fix that!

Writing our library

Inside my calculator.js file, I create a Revealing Module Pattern and assign it to the calculator variable.

Then, I create an add() function, and return it as an object property.

const calculator = (function () {    	function add () {  		  	}    	return {add};    })();  

Now my tests run, and everything is failing/red, just like we expect. Time to actually write some code!

I'm going to add a rest parameter called nums for all of the numbers the user provides. Inside the add() function, I'll define a starting total of 0.

Then, I'll loop through each num, add it to the total, and return that total back out.

function add (...nums) {  	let total = 0;  	for (let num of nums) {  		total = total + num;  	}  	return total;  }  

If we jump over to the testing suite, all of our tests are now passing!

Catching unexpected errors with TDD

That one was pretty straightforward.

But let's look at an example where TDD catches an error we might have missed (or that would have taken us longer to figure out) using other approaches.

First, we'll add tests for the subtract() method.

We can copy/paste the tests for the add() method, then update the function names and expected values. Here are the behaviors I'd expect from this method…

  1. When I pass two or more numbers into it, each number should be subtracted from the first, and the total returned.
  2. When I pass in just one number, that number should be returned as-is.
  3. When I pass in no numbers at all, 0 should be returned.

And here's the updated tests.

describe('the subtract() method', function () {    	it('Should subtract two or more numbers', function () {  		expect(calculator.subtract(8, 3, 2)).to.equal(3);  	});    	it('Should return the original value when one number is provided', function () {  		expect(calculator.subtract(42)).to.equal(42);  	});    	it('Should return 0 if no numbers are provided', function () {  		expect(calculator.subtract()).to.equal(0);  	});    });  

Back in my calculator.js file, I copy/paste my add() function, change the name to subtract(), and replace my additional operator (+) with the subtraction operator (-).

Then, I return it back out.

const calculator = (function () {    	function add (...nums) {  		let total = 0;  		for (let num of nums) {  			total = total + num;  		}  		return total;  	}    	function subtract (...nums) {  		let total = 0;  		for (let num of nums) {  			total = total - num;  		}  		return total;  	}      	return {add, subtract};    })();  

If we jump back over and run our tests… the first two fail!

The tests tell use that we're getting negative numbers when we expect positive ones to be returned.

AssertionError: expected -13 to equal 3  

Fixing the errors

Looking at our code, I quickly realize that we're not subtracting subsequent numbers from the first. We're subtracting them from 0, so the output is always negative.

To fix that, I use the Array.prototype.shift() method to get the first number from our nums array and assign it to the total variable instead of 0. This also removes that number from the nums array.

function subtract (...nums) {  	let total = nums.shift();  	for (let num of nums) {  		total = total - num;  	}  	return total;  }  

Now, when we loop through our array, each number is subtracted from the first.

If we run our tests again… the first two tests pass, but the last one fails! The tests tell use that we're getting undefined when we expected 0 to be returned.

AssertionError: expected undefined to equal +0  

Looking at the code, I realize that if we pass in no numbers at all, the nums array is empty, so no number is assigned to total.

let total = nums.shift() || 0;  

I use the or operator (||) to update total to use 0 if no value is otherwise assigned to it.

Now, all of our tests pass.

Refactor

Now that all of my tests are passing, I can refactor my code to make it easier to read and maintain.

I might add JSDoc comments to describe the parameters and what's returned. I might rename some variables, or use a ternary operator instead of the or operator in my subtract() method.

The specifics—the implementation details—don't matter much.

They'll vary from dev-to-dev and team-to-team. As long as the behavior of the library is the same, your tests will continue to pass, regardless of what you change under-the-hood.

Moar testing?

Now that this has finally clicked for me, I'm going to be writing tests a lot more. Hopefully you will, too!

You can download the source code from today's article on GitHub.

And if there's a topic you want to hear more about, send me an email and let me know!

Cheers,
Chris

Want to share this with others or read it later? View it in a browser.

Share :

Facebook Twitter Google+
0 Komentar untuk "[Go Make Things] TDD for JavaScript with buildless Mocha and Chai"

Back To Top