- [Instructor] There are a number
of different ways to test software.
The most common techniques are unit testing,
integration testing, and functional testing.
Let's explore each in order as they build upon each other.
Before I define unit testing,
it's good to know what a unit is.
A unit is the smallest testable part of an application.
The size depends on how the application was built.
In a procedural application,
it can be an entire module or an individual function.
For object-oriented applications,
it can be as big as an interface, like a class,
or more often an individual method.
With that in mind, let's explore unit testing
which tests a completely isolated unit of code
via its application program interface.
Unit tests are performed in memory,
meaning no permanent changes take place.
This makes them safe to run over and over again.
Additionally, unit tests execute very quickly
which makes them invaluable to development.
Unit tests are made up of assertions,
which validate the correctness of a unit.
And assertion is a statement that declares
that a predicate is going to be true.
A predicate, which is a logical term,
just returns boolean values, true or false.
If the validation fails, an assertion will throw an error
which will include context about what went wrong and where.
There are a number of different assertion methods
and the names will vary between testing frameworks.
The most basic is ok which checks to see
if the result of something is truthy.
There's equal which does a shallow comparison.
If working with an object,
deepEqual will perform shallow comparison of all properties.
If a more rigid comparison is needed,
strictEqual performs no implicit conversion.
If testing errors, throws looks for a specific thrown error.
Depending on the test framework,
there's a lot more methods
including opposites like, notEqual.
Here's a working example of a very basic unit test.
There's no need to open an editor at this time.
Node.js includes an assertion module named assert.
So, we'll require it.
Then, we'll define the tested method,
a simple function named cat
that returns the string literal, woof.
That seems a little odd, doesn't it?
Finally, let's perform an assertion that compares
the actual value and the expected value.
We'll assert with a method equal to validate
that invoking cat returns, meow.
When the script is executed,
the assertion module throws a new AssertionError
on the shallow comparison of woof and meow.
It turns out that woof is not the same as meow.
So, the assertion failed.
The native node.js assertion module works
and has increased its scope in recent versions
but it still can be limited in the available options.
We're going to explore and compare
unit testing tools in Chapter 3.
So, stay tuned.
That example is really simple
but what if there are dependencies?
The solution is to simulate the dependencies
without actually calling them.
This isolates the behavior of the tested unit
and prevents unexpected consequences.
For example, a unit may depend
on the result of a database query.
Instead of actually performing the query
and connecting to the database,
just return an arbitrary result each time.
Ultimately, the goal is to unit test only your custom code.
You don't have to worry about testing third-party code
as that is the responsibility of the maintainer.
This same goes for core code, like node.js itself.
If you'd like to write unit test for node core,
do it within the context of node,
not as separate project.
We'll explore replacing code and other similar techniques
in Chapter 4.
The next level of testing is integration testing,
which builds upon unit tests.
Instead of testing just a single unit,
integration testing combines units
and tests the resulting combinations.
Unlike the focus of unit tests on APIs,
integration tests can also include
user interfaces and the results,
such as database contents and logs.
Depending on the architecture of the application,
test can be on one system or across multiple systems.
Also, unlike unit tests,
integration testing uses either the full
or partial system environment,
including persistent layers
like databases or other similar services.
Therefore, integration tests are more complex
and harder to maintain than unit tests,
but are still very important.
What's an integration test look like?
Let's consider a hypothetical application with two units,
a reservation API and a client for that API.
To test user creation,
the reservation client would call the API.
Then, the API would return a response.
Then, you would validate the response returned by the API
and validate the created reservation.
Practically, writing integration tests
uses the same tools as unit testing.
The big difference is that it's across multiple units.
Functional testing is different from unit
and integration testing in a number of ways.
Instead of code, the focus is on the results
and the user interface.
A functional test checks a specific feature
and compares the results against the specification
from which it was created.
This is typically in the form of a user workflow,
complete with roles and steps.
Functional testing is typically automated
but manual is also an option.
Given that functional tests exercise features and not code,
they're slower to execute than unit and integration tests.
A functional test reads more like a script.
Here's a pseudocode example of a functional test
for a reservation creation workflow.
It starts when an anonymous user visits the front page.
Then, they click a link to Reservations.
They should see Book a table on the page.
They enter username into the form element for email.
Then, click the Request Booking button.
The Invalid Email message should be shown.
Next, enter username@example.com for the email address
and click the Request Booking button.
Finally, the Reservation created message
should be seen on the page.
If there's a failure in any of these steps,
then the test does not pass.
We're going to take a look at functional testing
in the context of Chapter 5.
Another kind of testing
that you may have heard of is system testing.
It's similar to integration testing
in that it exercises the relationships of components.
However, the major difference
is system testing uses the complete system,
including the application itself
and the hosting environment.
This finds defects both between individual pieces
and the system as a whole.
Many things happen in system testing
involving many tools and roles.
There's performance testing,
such as a stress test for reliability
and load testing for scalability.
Security scanners can find threats and vulnerabilities
which helps establish a baseline standard.
A usability check of the system
from someone with user experience
can help determine if it's easy to use.
You can check to see if a system is compatible
with its hosted environment
or other systems they may be working with.
In short, anything that requires the entire system,
both the application and the environment,
to be configured and running.
A final kind of testing that hasn't been mentioned
is regression testing.
Regressions are a kind of software bug
that occur when a feature stops working after a change.
Regression testing practically is just re-running unit
and integration tests after making a change.
Let's compare the four major types of testing.
Unit testing is against the API
of the smallest parts of the system in isolation.
Integration testing combines different parts
of the system together to ensure everything works.
Functional testing compares the results
of different workflows to the specifications.
System testing fully exercises the complete system
using many different tools and techniques
to make sure everything works as expected.
How do these different kinds of tests
relate to each other and in application?
I'm going to use a simplified example
of reservation creation again.
In code, there's a Reservation Model
that defines the structure of a reservation.
The Reservation Model persists in a third-party database
and checks to make sure that the results are stored.
The user interface is a custom Reservation Creation Form.
If valid, the reservation information
is sent to the model for storage.
Errors and successes are sent to the message display system,
which is also not custom code.
Unit tests, as you may recall, are performed in isolation.
So, none of the components communicate with each other.
Also, unit tests should only be on custom code
so the third-party code is excluded.
The two remaining units,
the Reservation Model and the Reservation Creation Form
can be covered with unit tests.
Integration tests are the next step up, they can involve
third-party components.
So, nothing is excluded.
Each integration test should be across components
that communicate with each other,
that means in integration test
for the Reservation Model and database,
the Reservation Creation Form and the Reservation Model
and finally the form and the message display system.
Functional tests test how the system works together
against the requirements.
Therefore, the components
are in full communication with each other.
Functional tests are against the user interfaces
so that includes the reservation creation form
and message display.
System tests include the same functionality
that is in functional tests and then some.
System tests are run against
the entire application and environment,
such as a security scan.
Those are a lot of testing options.
How can we automate these tests?