Fork our starter code repl.it and follow along as we implement a project using TDD.
We need to write a Node module to process transmissions from the Voyager1 probe.
Example
Transmission
"1410::<932829840830053761>"
Expected Result
{
id: 1410,
rawData: 932829840830053761
}
The features for this project have already been broken down into small testable units. Let's review them and then we will take it slow, one step at a time.
-1
if the transmission does NOT contain "::"
.id
propertyid
is the part of the transmission before the "::"
id
property should be of type Number
rawData
propertyrawData
is the part of the transmission after the "::"
rawData
if the rawData
part of the transmission does NOT start with <
and end with >
Requirement: Take in a transmission string and return an object.
To get started on this we need to:
Creating a blank test is easy, go to processor.spec.js
and add an empty test method.
Tests in Jasmine are declared with an it
function.
Remember that tests go inside of the describe
function, which along with the string
parameter describe the group of tests inside.
1describe("transmission processor", function() {
2
3 it("", function(){
4
5 });
6
7});
Give the test the name "takes a string and returns an object"
.
1describe("transmission processor", function() {
2
3 it("takes a string returns an object", function() {
4
5 });
6
7});
Now that we identified a clear goal for the test, let's add logic and expect
calls
in the test to verify the desired behavior. But wait... we haven't added anything
except an empty test at this point. There isn't any actual code to verify. That's okay,
this is part of the TDD process.
We are going to think about and visualize how this feature should be implemented in code. Then we will write out in the test how this new code will be used.
We need to think of something that will satisfy the statement
it("takes a string and returns an object"
.
The it
will be a function that is imported from a module. Below on line 2,
a processor
function is imported from the processor.js
module.
1const processor = require('../processor.js');
2
3describe("transmission processor", function() {
4
5 it("takes a string and returns an object", function(){
6
7 });
8
9});
We have an idea for a function named processor
and we have imported it.
Keep in mind this function only exists as a concept and we are writing a test
to see if this concept makes sense.
Now for the real heart of the test. We are going to use expect().toEqual()
to
verify that if we pass a string to processor
, an object is returned.
Carefully review lines 7 and 8 shown below.
1const processor = require('../processor.js');
2
3describe("transmission processor", function() {
4
5 it("takes a string and returns an object", function(){
6 let result = processor("9701::<489584872710>");
7 expect(typeof result).toEqual("object");
8 });
9
10});
On line 7 the processor
function is called, with the value being stored in a result
variable. On line 8 the result of the expression typeof result
is compared to the value
"object"
. Reminder that the typeof operator returns a string representation
of a type. If typeof result
evaluates to the string "object"
, then we know that processor
returned an object.
Let's run the test! Click the run >
button in your repl.it.
You should see an error about processor.js
not existing. This makes sense, because we have not
created the file yet. We are officially in the Red phase of Red, Green, Refactor!
Error: Cannot find module '../processor.js'
Now that we have a failing test, we have only one choice. Make it pass.
processor.js
file to your repl.it.processor
function that takes a parameter and returns an object.Contents of the new processor.js
file.
1function processor(transmission) {
2 return {};
3}
4
5module.exports = processor;
Run the test again.
We did it! 1 spec, 0 failures
means 1 passing
test. In repl.it you have to imagine the satisfying green color of a passing test.
1 spec, 0 failures
Finished in 0.011 seconds
This solution is very simple and does not need to be improved. The refactor step does not always lead to an actual changing of your code. The most important part is to review your code to make sure that it's efficient and meets your team's standards.
Requirement: Return -1
if the transmission does NOT contain "::"
.
Next we have a negative test requirement that tells us what should happen if the data is invalid. Before jumping into the code, let's review the steps we took to implement requirement #1.
Review of TDD process:
For requirement #2, the solution for steps 1 - 4 can be seen on lines 11 - 14 below.
1const processor = require('../processor.js');
2
3describe("transmission processor", function() {
4
5 it("takes a string and returns an object", function(){
6 let result = processor("9701::<489584872710>");
7 expect(typeof result).toEqual("object");
8 });
9
10 it("returns -1 if '::' not found", function(){
11 let result = processor("9701<489584872710>");
12 expect(result).toEqual(-1);
13 });
14
15});
Now for step 5, run the test and see it fail. When you run the tests, you should see the below
error message. Notice that -1
was the expected value, but the actual value was
and empty object, {}
.
Failures:
1) transmission processor returns -1 if '::' not found
Message:
Expected Input A to equal Input B
Next is step 6, write code that will make the test pass. Go to processor.js
and
update the processor
function to check the transmission
argument for the
presence of '::'
.
1function processor(transmission) {
2 if (transmission.indexOf("::") < 0) {
3 // Data is invalid
4 return -1;
5 }
6 return {};
7}
8
9module.exports = processor;
Lucky step 7 is to run the tests again. They should both pass.
2 specs, 0 failures
Finished in 0.035 seconds
Finally step 8 is to review the code to see if it needs to be refactored. As with the first requirement our code is quite simple and can not be improved at this time.
Requirement: Returned object should contain an id
property.
The id
is the part of the transmission before the "::"
The same steps will be followed, even though they are not explicitly listed.
See lines 16 - 19 to see the test added for this requirement. To test
this case not.toEqual()
was used, which is checking if the two values
are NOT equal. not.toEqual()
is used to make sure that result.id
is NOT equal to undefined
. Remember that if you reference a property on an
object that does NOT exist, undefined
is returned.
1const processor = require('../processor.js');
2
3describe("transmission processor", function() {
4
5 it("takes a string returns an object", function(){
6 let result = processor("9701::<489584872710>");
7 expect(typeof result).toEqual("object");
8 });
9
10 it("returns -1 if '::' not found", function(){
11 let result = processor("9701<489584872710>");
12 expect(result).toEqual(-1);
13 });
14
15 it("returns id in object", function() {
16 let result = processor("9701::<489584872710>");
17 expect(result.id).not.toEqual(undefined);
18 });
19
20});
The fail message looks a little different than what we have seen. The phrase "Expected 'actual' to be strictly unequal to" lets us know that the two values were equal when we didn't expect them to be.
Failures:
1) transmission processor returns id in object
Message:
Expected "actual" to not equal undefined
The object returned from processor
doesn't have an id
property. We need
to split the transmission on '::'
and then add that value to the object
with the key id
. See solution in processor.js
below.
1function processor(transmission) {
2 if (transmission.indexOf("::") < 0) {
3 // Data is invalid
4 return -1;
5 }
6 let parts = transmission.split("::");
7 return {
8 id: parts[0]
9 };
10}
11
12module.exports = processor;
Run the tests again. That did it. The tests pass! :-)
Line 6 splits transmission
into the parts
array, and line 8 assigns
the first entry in the array to the key id
.
3 specs, 0 failures
Finished in 0.011 seconds
Requirement: The id
property should be of type Number
.
Again the same steps are followed, though not listed.
New test to be added to specs/processor.spec.js
:
1it("converts id to a number", function() {
2 let result = processor("9701::<489584872710>");
3 expect(result.id).toEqual(9701);
4});
Fail Message
Failures:
1) transmission processor converts id to a number
Message:
Expected '9701' to equal 9701.
Convert the id part of the string to be of type number
.
1function processor(transmission) {
2 if (transmission.indexOf("::") < 0) {
3 // Data is invalid
4 return -1;
5 }
6 let parts = transmission.split("::");
7 return {
8 id: Number(parts[0])
9 };
10}
11
12module.exports = processor;
Now for the great feeling of a passing tests!
4 specs, 0 failures
Finished in 0.061 seconds
Note
You may be wondering what happens if that data is bad and the id can't be turned into a number. That is a negative test case related to this feature and is left for you to address in the final section.
Requirement: Returned object should contain a rawData
property. The rawData
is the part of the transmission after the "::"
New test to be added to specs/processor.spec.js
1it("returns rawData in object", function() {
2 let result = processor("9701::<487297403495720912>");
3 expect(result.rawData).not.toEqual(undefined);
4});
Fail Message
Failures:
1) transmission processor returns rawData in object
Message:
Expected "actual" to not equal undefined
We need to extract the rawData
from the second half of the transmission
string after it's been split. Then return that in the object.
1function processor(transmission) {
2 if (transmission.indexOf("::") < 0) {
3 // Data is invalid
4 return -1;
5 }
6 let parts = transmission.split("::");
7 let rawData = parts[1];
8 return {
9 id: Number(parts[0]),
10 rawData: rawData
11 };
12}
13
14module.exports = processor;
It's that time again, our tests pass!
5 specs, 0 failures
Finished in 0.041 seconds
Requirement: Return -1
for the value rawData
if the rawData
part of
the transmission does NOT start with <
and end with >
.
Let's think about what test data to use for this requirement. What ways could the transmission data be invalid?
<
at the beginning>
at the end<
and >
<
but the symbol is in the wrong place>
but the symbol is in the wrong placeAll these cases need to be covered by a test. Let's start with #1, which
is missing <
at the beginning.
New test to be added to specs/processor.spec.js
1it("returns -1 for rawData if missing < at position 0", function() {
2 let result = processor("9701::487297403495720912>");
3 expect(result.rawData).toEqual(-1);
4});
Fail Message
Failures:
1) transmission processor returns -1 for rawData if missing < at position 0
Message:
Expected values to be equal
Now add new code to processor.js
to make the tests pass. Note that we don't
simply return -1
, the requirement is to return the object and set the value
of rawData
to -1
.
1function processor(transmission) {
2 if (transmission.indexOf("::") < 0) {
3 // Data is invalid
4 return -1;
5 }
6 let parts = transmission.split("::");
7 let rawData = parts[1];
8 if (rawData[0] !== "<") {
9 rawData = -1;
10 }
11 return {
12 id: Number(parts[0]),
13 rawData: rawData
14 };
15}
16
17module.exports = processor;
You know what's next, our tests pass!
6 specs, 0 failures
Finished in 0.056 seconds
Try It!
The test data we used was missing <
at the beginning. Add tests
to cover these cases. -1
should be returned as the value for
rawData
for all of these.
"9701::8729740349572>0912"
"9701::4872<97403495720912"
"9701::487297403495720912"
"9701::<487297403495<720912>"
Use the steps demonstrated above to implement all or some of the features below. Take your time, you can do it!
transmission
.-1
if the id
part of the transmission
cannot be converted
to a number.-1
if more than one "::"
is found in transmission
.< >
symbols in the value assigned to rawData
.-1
for the value of rawData
if anything besides numbers are
present between the < >
symbols.