Start course

This Course will introduce new software developers to the concepts of software testing, debugging, and logging. These concepts are common to most programming languages, making this foundational knowledge.

Learning Objectives

  • The purpose of unit testing
  • The purpose of integration testing
  • The concept of code coverage
  • The concept of software debugging
    • And some of the different debugging techniques
  • The concept of software logging
    • And why some information shouldn’t be included inside log entries

Intended Audience 

  • New software developers


  • You should have at least a conceptual understanding of programming
    • It’s okay if you’re not yet developing complex software
    • As long as you’re comfortable with the concept of functions, classes, and methods, you’re likely ready for this Course

The goal for this course was to introduce new software developers to the concepts of software testing, debugging, and logging

These concepts exist across different programming languages, frameworks, tools, etc. Making these concepts rather generally applicable. 

During this course we talked about:

  • The purpose of unit testing

  • The purpose of integration testing

  • The concept of code coverage

  • The concept of software debugging

    • And some of the different debugging techniques.

  • The concept of software logging

    • And why some information shouldn’t be included inside log entries.

Let’s go through each of these points and summarize the key takeaways.

  • The purpose of unit testing is to validate the code logic inside an individual unit of code.

Unit tests validate that given some specific input the unit will return the expected output. When a unit of code passes all of its test cases it’s considered ready for production.

Software is built by combining multiple units of code that all share the same purpose. By testing all of these individual units we can gain a higher level of confidence that combining them will yield the desired results.

Units are intended to be run quickly and in isolation. Which is why external resources such as network connections, databases, files, etc, are replaced with stand-in implementations.

  • The purpose of integration testing is to ensure that disparate units of code work well together towards their shared purpose.

Integration testing is where developers can ensure that the holistic application behaves as expected. Once all of the individual units of code are tested they’re ready to be combined and to interact with external resources such as a database.

Integration testing is intended to be performed using non-production systems and services. Tests should be performed using isolated copies of any required external resources. 

The focus of integration tests is to verify that holistic processes behave as expected. For example: does your home page return a response status of 200 and the text ‘welcome’ in the HTML?

  • Code coverage is a metric used to describe the percentage of code executed when running a collection of tests.

Code coverage is a useful contextual metric because in certain cases knowing how much of the code has been executed during a test can help provide a higher level of confidence in the code; since more of the code has been tested.

The amount of optimal code coverage for a given piece of software depends on the use case. Important systems require a higher level of code coverage in order to provide the desired level of confidence in the system. 

Edge cases and bugs are going to occur in software. Testing can help identify those defects, as well as ensure that already solved bugs aren’t re-introduced. 

  • The purpose of debugging is to remove software defects from a piece of software.

Bugs are found as developers are adding new functionality; they’re also found when software is tested, and when it’s running in production. Debugging is a regular part of the development process. 

There are different debugging techniques for different types of systems. However, there’s a debugging method called caveman debugging which is so primitive that it’s likely familiar to most developers.

Caveman debugging consists of using a print or display function to display basic text-based information to a terminal’s standard output.

This is a really easy technique to use when learning a new system or programming language.

There’s an entire world of specific debugging tools. Debuggers can take debugging to the next level. They can step through code one line at a time and inspect or edit variables. They’re usually such low level tools that they can do all kinds of interesting things. 

Learning to use a debugger can be a sizable time investment. However, if you find that you’re spending a fair amount of time debugging with other methods, it might be a worthwhile investment.

  • The purpose of logging is to capture application messages so that we can gain insights into how an application is behaving.

Spending a lot of time debugging can help to form an appreciation for good log entries. Logs can provide us with insights into what an application is doing while the application is running.

Log entries tend to include a timestamp, a message, and a log level. Log levels include options such as debug, info, warning, and error. Developers can specify the message format; allowing formats such as JSON to be used if the logs are being consumed programmatically. Basic text messages are common when the logs are consumed by people. 

Log entries are intended to provide insight into the running software. Log entries should help paint a picture to the log consumer about the behavior of the software. And that picture is highly influenced by the log level. 

For example: debug level messages might display certain arguments in the log if they’d be useful when debugging. 

An info level message might include something such as “database update successful.” Knowing that a common, but important action was successful can be helpful when debugging. If you need to comb through logs to help debug an error then knowing that certain processes completed successfully can help to narrow down the exact location of a bug.

A warning level message might include something such as “low drive space. 30% space remaining.” Warnings help to prevent future problems. 

An error level message tends to include a display of the exact error that occurred. This is commonly a stack trace showing the chain of code that ran prior to the error. Error level messages should help developers to recreate what happened. 

The exact message is going to depend on the application and its use case. However, it’s important to ensure that the sensitive information isn’t being included in the log entries. 

This is more than just a best practice. The handling of sensitive information is commonly covered under data governance laws. If your code interacts with sensitive information such as credit card numbers, I recommend that you research what might be required of you.

I hope this course has helped you to gain a general understanding of software testing, debugging, and logging. If you’re interested in learning more about topics for your given programming language here are my recommendations.

  1. Investigate the commonly used unit and integration testing tools.

    1. The variation among some of these tools is noteworthy. 

    2. Make sure to research a few of the common options and see if any resonate with you. 

    3. Sometimes a tool aligns your mental model and it makes it much easier to use that tool. 

    4. So find the option that sparks joy. 

  2. Investigate the commonly used debuggers.

    1. I prefer using command line based debuggers. 

    2. Your preference may differ.

    3. If you’re fortunate enough to have options with your language, find the one that sparks joy. 

    4. Perhaps joy might be a bit strong for a debugger. Perhaps find the one that doesn’t induce rage. :P 

  3. Investigate the commonly used logging modules.

    1. I try to use the logging module provided by the language I’m using.

    2. However, sometimes third-party modules provide enough value to justify the addition of a dependency. 

    3. Get to know the common options for your language and attempt to use the one that introduces the least amount of complexity to the software.

Alright, that’s going to be all for this lesson, and therefore this course. Thank you so much for watching. I hope this has been helpful, and I’ll see you in another course!

About the Author
Learning Paths

Ben Lambert is a software engineer and was previously the lead author for DevOps and Microsoft Azure training content at Cloud Academy. His courses and learning paths covered Cloud Ecosystem technologies such as DC/OS, configuration management tools, and containers. As a software engineer, Ben’s experience includes building highly available web and mobile apps. When he’s not building software, he’s hiking, camping, or creating video games.

Covered Topics