Understanding Unit Testing Basics
Understanding Unit Testing Basics
In this lecture we’re going to start the course on Test Driven Development by defining what unit testing actually is. We’ll go over some of the common types of software
testing, look more closely at the specifics of unit testing, and review a simple example.
WHY DO WE UNIT TEST?
<click> Because software bugs can hurt the business! We don’t want any bugs making it out to the field for our customers to see as that can hurt our reputation and
cause customers to look at using other products.
<click> Software testing attempts to address this problem by catching any bugs in the software before they get to the field. <click> This is done systematically with a
multi-layered approach where each layer of testing provides a safety net for catching bugs before they get to the field.
LEVELS OF TESTING
• Unit Testing - Testing at the function level.
There are several levels of testing which provide the layers of safety nets for catching any bugs that might be in the code.
- The lowest level are Unit Tests. Unit tests validate individual functions in the production code. Unit tests are generally the most comprehensive tests which should test
all positive and negative test cases for a function.
- Next comes component level testing which tests the external interfaces for individual components. Components are essentially a collection of the functions.
- Then comes system level testing which tests the eternal interfaces at a system level. Systems can be collections of component or of sub-systems.
- Lastly comes performance testing, which tests systems and sub-systems at expected production loads to verify that response times and resource utilization (i.e.
memory, CPU, disk usage) are acceptable.
UNIT TESTING SPECIFICS
• Tests individual functions
• A test should be written for each test case for a function (all
positive and negative test cases).
• Groups of tests can be combined into test suites for better
organization.
- Each test case for the function should have a corresponding unit test.
- Groups of unit tests can be combined into test suites which can help with organizing the tests.
- The unit tests should execute in your development environment rather than the production environment. This is important to ensure you can run them easily and often.
- And lastly the unit tests should be implemented in an automated fashion. You should be able to click a button and the unit tests will build and execute.
A Simple Example
import pytest
# Production Code
def str_len( theStr ):
return len(theStr)
# A Unit Test
def test_string_length():
// Setup
testStr = “1”
result = str_len(testStr) // Action
assert result == 1 // Assert
Here we have a very simple example showing a unit test for a bit of production code. The production code is a function that returns the length of a passed in string. The
unit test is a single positive test case that verifies a length of 1 is returned for a string with one character in it. The “test_str_len” call is the unit test for the str_len
production code. The unit test performs three steps:
- An action step where it calls the production code to perform the action that is under test.
- An assertion step where the test validates the results of the action.
This is a common structure that all of your unit tests should follow.
SUMMARY
• Unit tests are the first safety net for catching bugs
before they get to the field.
• Unit tests validate test cases for individual functions.
<click> Unit tests are our first safety net for catching bugs in the production code
<click> Unit tests should run fast! We ideally want a developer re-running the unit tests every 3-5 minutes and this can be difficult with a slow build processes or if any of
the tests run slow (i.e. more than a few seconds).
The development environment influences the execution of unit tests by ensuring they can be run frequently and easily, which is crucial for early detection of bugs. Running unit tests in the development environment rather than the production environment allows developers to quickly iterate on tests and production code, facilitating rapid feedback and continuous integration. Automated unit tests should be easy to execute with minimal setup, ensuring they provide timely and relevant feedback to developers .
Unit testing is implemented primarily to catch software bugs before they reach the field, thus protecting the business reputation and preventing customers from seeking alternative products. It provides a systematic, multi-layered approach to quality assurance with unit tests serving as the first safety net for identifying bugs in the production code before delivering the software to end users .
Unit tests balance testing accuracy and execution speed by focusing on discrete, isolated functions that evaluate specific test cases quickly. Best practices to achieve this balance include writing independent tests that do not rely on external systems, using mocks or stubs where possible, and ensuring tests are concise and directly related to function specifications. This ensures that tests run quickly, typically within seconds, allowing for frequent execution without significantly impacting the development pace .
The metaphor of unit tests as a 'safety net' encapsulates their role in catching bugs early in the software development process, preventing them from escalating to later stages where they are more costly and complex to fix. Like a safety net in a physical sense, these tests provide a foundational layer of quality assurance, ensuring that individual functions behave as intended, which is critical for maintaining overall system integrity. Without this net, issues might go unnoticed until they cause significant disruptions, making unit tests essential for reliable software delivery .
To ensure unit tests are effective in verifying function-level behavior, guidelines include writing a separate test for each significant behavior of a function, ensuring tests are self-contained and do not depend on external systems or shared state, using clear and descriptive test names for better readability, and maintaining atomic tests that focus on one aspect at a time. Additionally, tests should be maintained alongside the production code to evolve with the application, ensuring they remain relevant and accurate .
A well-structured unit test involves three main steps: setup, action, and assertion. In the setup step, the necessary conditions for the test are created, such as preparing test data. The action step involves executing the function or code being tested to perform the action under test. Finally, the assertion step checks the results of the action against expected outcomes to determine if the test passes. Each step is crucial for establishing a clear and organized approach to testing that ensures the test accurately verifies the function's behavior .
Organizing unit tests into test suites improves the testing process by categorizing tests into logical groups that mirror the structure of the codebase, making them easier to manage and navigate. Test suites help in running related tests collectively, identify coverage gaps, and maintain coherence between tests and the components they validate. This organization helps in systematic test execution, enhances debugging, and facilitates consistent test maintenance across development cycles .
Unit testing contributes to the overall software testing strategy by validating individual functions in the code, ensuring that each function performs as expected. It is the most comprehensive testing level as it aims to cover all positive and negative test cases for each function. However, unlike system or performance testing, unit testing does not test the interaction between various components or the system's behavior under typical load conditions, thereby limiting its scope to identifying functional issues in isolation rather than integration or performance issues .
The automated aspect of unit testing enhances its effectiveness by providing consistent, fast, and repeatable testing processes that can be easily integrated into the development workflow. Automated tests reduce the likelihood of human error, enable frequent test execution, and ensure that tests are run under the same conditions every time, which is difficult to achieve with manual testing. This automation facilitates rapid feedback and allows developers to modify code with greater confidence knowing that tests will catch any unintended effects .
Challenges associated with implementing unit tests include ensuring comprehensive test coverage for all test cases, maintaining tests as the codebase evolves, and managing the balance between fast execution and detailed testing. These challenges can be mitigated by adopting practices such as continuous integration to frequently run tests, keeping tests and code synchronized through refactoring, and designing tests to be efficient and targeted while avoiding overlap between different test levels .





