Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (2024)

By: [emailprotected]

| February 28, 2024

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (1)

What is Unit Testing, Tutorial and 14 Best Practices

When I started in this industry, only anavant-gardefringe unit wrote automated tests for their code. Over the last 15 years, however, that number has exploded, and the practice has become mainstream. But “mainstream” does not mean “universal.” Plenty of folks still do not have comfort with or even exposure to the unit testing practice. And yet, a form of peer pressure causes them to play that close to the vest.

So I reached out to these folks to say, “Hey, no worries. You can learn, and you don’t even have to climb too steep of a hill.” I’d like to revisit that approach again, here, today, and in the form of a blog post.

Let’s get started with unit testing in C#, assuming that you know absolutely nothing about it.

What Unit Testing Isn’t

First, let’s clear up any misconceptions by talking about what doesn’t count. Not every test you could conceivably write qualifies as a unit test.

If you write code that stuffs things into a database or that reads a file from disk, you have not written a unit test. Unit tests don’t deal with their environment and with external systems to the codebase. If it you’ve written something that can failwhen run on a machine without the “proper setup,” you haven’t written a unit test.

Unit tests also don’t count as other sorts of tests. If you create some sort of test that throws thousands of requests for a service you’ve written, that qualifies as a smoke test and not a unit test. Unit tests don’t generate random data and pepper your application with it in unpredictable sequences. They’re not something that QA generally executes.

And, finally, unit tests don’t exercise multiple components of your system and how they act. If you have a console application and you pipe input to it from the command line and test for output, you’re executing an end-to-end system test — not a unit test.

Make no mistake — tests that do these things add value. They should be part of your general approach to code quality. They just don’t fall under the heading of unit tests.

What Unit Testing Is

With that out of the way, let’s consider what actually does qualify. Unit tests isolate and exercise specificunitsof your code. Okay. I’ll concede that I just punted by defining a term with a word in the term. But the creators of the term left the designation deliberately vague, presumably to cross language boundaries.

In C#, you can think of a unit as a method. You thus write a unit test by writing something that tests a method. Oh, and it tests something specific about that methodin isolation. Don’t create something called TestAllTheThings and then proceed to call every method in a namespace.

Write better code on your workstation, try Stackify’s free code profiler, Prefix. Prefix works with .NET, Java, PHP, Node.js, Ruby, and Python.

That’s really it — all there is to it. You’ll notice that I haven’t mentioned a few things that might pop into your head, such as test-driven development (TDD), unit test frameworks, test runners, mocks, or other unit testing tools. Let’s not get ahead of ourselves. Forget TDD and mocks for another time, as those are separate topics. And forget test runners and frameworks for now. We will get to those, but they aren’t, strictly speaking, necessary to havea unit test.

Unit Testing Tutorial

A Dead Simple Unit Test

For the rest of this post, I’m going to demonstrate unit testing with a hypothetical and fairly trivial calculator class. For now, let’s just have it do the following:

public class Calculator{ public int Add(int x, int y) { return x + y; }}

That’s it. We have a single class, Calculator, in a class library project. Add looks pretty reliable at first glance, but so does all the code you write. You still need to test it, if only to prove your rightness to others.

To do that, let’s add a new console project and give it a reference tothe project containing calculator, like so.

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (2)

Now, let’s do the following in the main of the Calculator Tester.

class Program{ static void Main(string[] args) { var calculator = new Calculator(); int result = calculator.Add(5, 6); if (result != 11) throw new InvalidOperationException(); }}

Congratulations! You’ve just written one extremely clunky unit test!

Introducing a Unit Test Framework

I’m sure you can pick out the flaw in this approach. While you might enjoy your newfound ability to verify that 5 + 6 does, indeed, return 11, you probably don’t want to create and run a new console project for each unit test that you write.

Now, you might think that you could create a method for each test and call it from the main in CalculatorTester. That would improve things over creating a project for each test, but only pain waits down that road (more than a decade ago, I used to test this way, before ubiquitous test runners existed). Adding a method and call for each test will prove laborious, and tracking the output will prove unwieldy.

Fortunately, you can do better. Since one or two other people have tried unit testing before you picked it up, some enterprising folks built frameworks to make it easier. Let’s take a look at how to do that.

First, I’m going to delete my short-lived CalculatorTester console project. Next, I’ll right-click on the solution to add a project, and choose the “Unit Test Project Template” after selecting “Test.”

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (3)

Add the project reference to Calculator again, and then take a look at the class it created for you, called “UnitTest1.” We’ll do a little better with the naming later, but let’s write our first, real test.

[TestClass]public class UnitTest1{ [TestMethod] public void TestMethod1() { var calculator = new Calculator(); int result = calculator.Add(4, 3); Assert.AreEqual(7, result); }}

Now, we’ve got an actual unit test! Click Ctrl-R, T to run it, and look what happens.

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (4)

We can see that it ran, passed, and took 8 ms to execute. Much better out of the box than our erstwhile console application, eh?

Anatomy of a Unit Test

Let’s do a brief post-mortem in the form of looking at the bits and pieces of this test. But first, let’s do a bit of renaming.

[TestClass]public class CalculatorTests{ [TestMethod] public void Adding_4_And_3_Should_Return_7() { var calculator = new Calculator(); int result = calculator.Add(4, 3); Assert.AreEqual(7, result); }}

First off, we have a class called “CalculatorTests,” indicating that it will contain tests for the calculator class. (Other ways to conceptually organize your tests exist but consider that an advanced topic.) It gets an attribute called “TestClass” to tell Visual Studio’s default test runner and framework, MSTest, that this class contains unit tests.

Then, we have a method now called “Adding_4_And_3_Should_Return_7.” I’d say that title speaks for itself, and that’s kind of the idea. We want to name our test methods in a very descriptive way that indicates our hypothesis as to what inputs should create what outputs. Notice the TestMethod attribute above this method. This tells MSTest to consider this a test method. If you removed the attribute and re-ran the unit tests in the codebase, MSTest would ignore this method. You need to decorate any test classes and test methods this way to make MSTest execute them.

Finally, consider the static method Assert.AreEqual. Microsoft supplies a UnitTesting namespace with this Assert class in it. You use this class’s various methods as the final piece in the MSTest puzzle. Assertion passes and fails determine whether the test passes or fails as seen in the test runner. (As an aside, if you generate an unhandled exception in the test, that constitutes a failure, and if you never assert anything, that constitutes a pass.)

The Importance of Unit Testing

Here are some reasons why every developer should make unit testing a mandatory practice:

  • Catching Bugs Early: By writing unit tests, developers can identify bugs and errors in their code much earlier in the development cycle, before they propagate to other parts of the application. This can save time and money in the long run by reducing the cost of fixing bugs later in the development cycle.
  • Improving Code Quality: Unit testing can help improve code quality by ensuring that each unit of code is functioning correctly and as expected. This helps to prevent errors and unexpected behavior in the application.
  • Frequent Releases: By testing code in isolation, developers can quickly identify and fix issues, allowing for faster iteration and more frequent releases.
  • Encouraging Good Programming Habits: Writing unit tests encourages good programming habits, such as writing code that is modular, easy to understand, and testable. This can help to improve the overall quality of the codebase.

Now that we’ve understood what unit testing is and why it’s important, let’s look into some unit testing best practices.

Unit Testing Best Practices

1.Arrange, Act, Assert

Let’s now consider another sort of unit test anatomy. Here, I’m talking about the logical components of a good unit test. The test that I’ve written has them in their absolute most basic form. Perhaps not surprisingly, given the title of this section, those components are “arrange, act, assert.”

Lean heavily on thescientific methodto understand the real idea here. Consider your test as a hypothesis and your test run as an experiment. In this case, we hypothesize that the add method will return 7 with inputs of 4 and 3.

To pull off this experiment, first, wearrangeeverything we need to run the experiment. In this case, very little needs to happen. We simply instantiate a calculator object. In other, more complex cases, you may need to seed an object with some variable values or call a particular constructor.

With the arranging in place, weact. In this case, we invoke the add method and capture the result. The “act” represents the star of the unit testing show. All of the arrangings lead up to it, and everything afterward amounts to retrospection.

Finally, weassert. The invocation of the Assert class probably gave that one away. But theassertconcept in the unit test represents a general category of action that you cannot omit and have a unit test. It asserts the hypothesis itself. Asserting something represents the essence of testing.

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (5)

2. Use Relevant and High-Quality Test Data

Test with data that is similar to the data that the application will handle in the production environment. This approach can help identify issues that may not be apparent with small or simplistic test data. You should ensure that the test data covers a variety of scenarios, including edge cases and invalid input. You should also consider using real-world data whenever possible. For example, if an application is designed to process financial transactions, the test data should include realistic transaction amounts, dates, and customer information.

When testing with representative data, it’s important to avoid hard-coding data values in the test cases. This approach can make the test cases brittle and difficult to maintain.

3. One Assert Per Test Method

I may catch some flak for this from unit-testing veterans of a certain testing philosophy, but so be it. Not everyone will necessarily agree with this, but I believe you should shoot for one assert per test method. Each test forms a hypothesis and asserts it. (The contrarian viewpoint would argue that multiple asserts can represent a single hypothesis).

I won’t go so far as to say that no test shouldevercontain a number of assertions other than one. But I will say that your unit test suite should have a test-to-assert ratio pretty darned near 1.

Unit testing newbies commonly make the mistake of testing all of the things in one test method. After all, more testing is better, right? This drives them to want to get the most bang for their buck with each test, asserting lots of stuff.

But, remember, hypothesis, experiment. Think of reading the output of the test in the test runner. If you assert 20 things, you still only see a single failure. How will you know at a glance what went wrong — which of your 20 assertions failed?

4. Avoid Test Interdependence

Each test should handle its own setup and tear down. The test runner will execute your stuff in whatever order it pleases and, depending on the specific runner you use (advanced topic), it might even execute them in parallel.

You, therefore, cannot count on the test suite or the class that you’re testing to maintain state in between tests. But that won’t always make itself obvious to you.

If you have two tests, for instance, the test runner may happen to execute them in the same order each time. Lulled into a false sense of security, you might come to rely on this. Weeks later, when you add a third test, it upsets this mix and one of your tests starts failing intermittently because of the ordering.

This will confuse and infuriate you. Avoid this interdependence at all costs.

5. Write Tests before Code

Writing tests before writing code is a practice called Test Driven Development (TDD). This approach can help ensure that the code is testable, meets requirements, and is more maintainable.

With TDD, you create test cases before writing the actual code, and then write the code to pass those tests. This approach can also help you identify any gaps in the requirements or logic before writing any code. Additionally, TDD can help teams avoid the cost of fixing defects and bugs later in the development cycle.

6. Keep It Short, Sweet, and Visible

I’ve trod this road before as well and felt the pain. Resist the impulse to abstract test setup (the “arrange”) to other classes, and especially resist the impulse to abstract it into a base class. I won’t say that you’llneverfind this abstraction appropriate (though I’d argue base classes are never appropriate here), but look to avoid it.

The reasoning here is simple. When a test fails, you want to understand what went wrong. You thus want a test where all setup logic reveals itself to you at a glance. If you have logic strewn all over the class or residing in different classes, you’ll have a defect treasure hunt on your hands. That’s bad enough in prod code, but tests are supposed to help eliminate that. Make it easy on yourself.

7. Use Headless Testing when Appropriate

Headless testing is a form of running tests without a user interface or browser window. This approach can be more efficient and reliable than running tests with a graphical interface, especially for tests that do not require visual interaction.

You can use tools such as PhantomJS or Headless Chrome to run tests in a headless environment. These tools can help reduce the overhead of running tests with a graphical interface and can allow tests to run more quickly and in a more consistent environment.

However, it’s important to note that not all tests are suitable for headless testing. Tests that involve visual interaction or rendering, such as tests for user interfaces or web page layout, may require a graphical interface to be properly tested. Therefore, you should carefully consider which tests are appropriate for headless testing and which require a graphical interface.

8. Test Positive and Negative Scenarios

It’s important to test both the expected outcomes and unexpected outcomes of a function or method. Negative tests are especially important to ensure that error handling and edge cases are handled correctly.

Developers should also test for boundary conditions, such as the minimum and maximum values of input variables. By testing both positive and negative scenarios, you can ensure that the code is robust and can handle unexpected situations.

9. Use Mock Objects when Necessary

Mock objects can be used to simulate dependencies, such as databases or web services, which can make testing more reliable and faster. By using mock objects, developers can isolate the code being tested and focus on the behavior of the unit being tested. This approach can also help avoid issues with external systems, such as network connectivity or performance.

10. Ensure Compliance with Industry Standards

Ensuring compliance with industry standards involves verifying that the application meets the requirements set by relevant regulations or standards, such as HIPAA or PCI-DSS. Compliance testing can help ensure that the application is secure and that sensitive data is protected.

You should first identify the relevant standards or regulations that apply to your application. You should then create test cases that cover all relevant requirements. For example, if the application is required to encrypt all sensitive data in transit and at rest, the test cases should verify that the encryption is implemented correctly.

It’s important to document the compliance testing process thoroughly. This documentation can be used to demonstrate compliance with regulators or auditors.

11. Ensure Tests are Repeatable

Tests should be repeatable, meaning that they produce the same result every time they are run. This approach helps ensure that the tests are reliable and can be used to detect regressions. To ensure repeatability, you should use deterministic tests that don’t depend on external factors, such as the current time or the state of the system.

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (6)

12. Test for Security Vulnerabilities

This involves identifying and addressing potential security issues in the application and can help ensure that the application is secure and can handle potential attacks.

You should consider testing for common vulnerabilities, such as SQL injection, cross-site scripting, or buffer overflows, and also use techniques such as fuzz testing to uncover potential issues. Fuzz testing involves sending random or malformed input to the application to see how it responds. This approach can help you identify unexpected behavior that could be exploited by attackers.

It’s important to test for security vulnerabilities throughout the development process, not just at the end. By testing early and often, you can identify and address potential issues before they become more difficult and expensive to fix.

13. Recognize Test Setup Pain as a Smell

For my second to last best practice mention, I’ll get a bit philosophical. Stick this one in the back of your head, but do not forget it.

When you first teach yourself to write unit tests, you’ll do so on toy codebases like my little calculator. It’ll take some real-world experience and some notches on your belt before you hit this, but you will hit it. And, when you do, remember my advice.

If you find that the “arrange” part of your unit test becomes cumbersome, stop what you’re doing. One of the most undercover powerful things about unit tests is that they provideexcellentfeedback on the design of your code — specifically its modularity. If you find yourself laboring heavily to get a class and method setup so that you can test it, you have a design problem.

When you create setup heavy tests, you createbrittletests. Tests carry a maintenance weight, just like production code. You thus want to avoid unwieldy tests like the plague — they’ll break and make you and everyone else hate them. So instead of going nuts on the setup, take a critical look at your design.

14. Add Them to the Build

I’ll conclude the post with arguably the most important best practice. Since you’re early in your unit testing journey, get started on this one immediately when you only have a single test in your codebase.

If your team has a continuous integration build, add your new unit test suite’s execution to the build. If any tests fail, then the build fails. No exceptions, no ifs, ands or buts. Trust me on this one. If unit test failures don’t stop your team’s progress, your team will eventually start ignoring the failures when the pressure to deliver mounts. It’s not a question of if, but when.

Unit testing takes time to learn and even more time to master. Getting to that mastery will seem incredibly onerous at first, but you won’t ever get there if you don’t go all in. If you’re going to write ’em, make ’em count.

Since the topic of unit testing has grown in demand, I decidedto write abook about unit testing.

Related posts:

  • Unit Test Frameworks for C#: The Pros and Cons of the Top 3
  • How to Write Test Cases and Why They Are Like the Scientific Method
  • The Ultimate Guide to Performance Testing and Software Testing: Testing Types, Performance Testing Steps, Best Practices, and More
  • Continuous Testing Requires Faster Feedback Loops

Improve Your Code with Retrace APM

Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.

  • App Performance Management
  • Code Profiling
  • Error Tracking
  • Centralized Logging

Learn More

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (12)

Author

[emailprotected]

More articles by [emailprotected]

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed (2024)

FAQs

Unit Testing Tutorial: 6 Best Practices to Get Up To Speed? ›

Use Mock Objects when Necessary

Mock objects can be used to simulate dependencies, such as databases or web services, which can make testing more reliable and faster. By using mock objects, developers can isolate the code being tested and focus on the behavior of the unit being tested.

How can I improve my unit test speed? ›

Use Mock Objects when Necessary

Mock objects can be used to simulate dependencies, such as databases or web services, which can make testing more reliable and faster. By using mock objects, developers can isolate the code being tested and focus on the behavior of the unit being tested.

How can I speed up my test execution? ›

Faster Testing | 10 Ways To Speed Up Testing
  1. 4.1 ‍1. Optimize the CI/CD Pipeline.
  2. 4.2 ‍2. Start Tracking Bugs Early.
  3. 4.3 ‍3. Implement Parallel Testing.
  4. 4.4 ‍4. Get the Testing Process Organized.
  5. 4.5 ‍5. Balance Manual and Automation Testing.
  6. 4.6 ‍6. Reduce Flakiness in the QA Process.
  7. 4.7 ‍7. Test on Real Devices.
  8. 4.8 ‍‍8.
May 21, 2024

Which three items are best practices for unit test? ›

Arrange, Act, Assert is a common pattern when unit testing. As the name implies, it consists of three main actions: Arrange your objects, create and set them up as necessary.

Why is it a good practice to build a unit testing class? ›

Unit testing helps ensure your code works as intended. However, you can learn why a unit fails to pass only if you write simple and readable tests. It is easier to write, maintain, and understand simple test cases. Additionally, simple tests are easier to refactor.

How can I increase my speed in test? ›

After each mock test, investigate your order of attempting questions. The third approach is to leave the questions where you are stuck or which is taking more time. This will help you to improve speed and maximize the number of attempts. When you examine the mocks, check which questions you wasted time on.

How could I improve my speed? ›

Incorporate exercises like planks, lunges, and squats into your workout routine to build strength and stability. Additionally, consider adding plyometric exercises, such as box jumps or bounding, to help increase your leg power and explosiveness. Remember, a strong and stable body is the foundation for increased speed.

How can I maximize my speed test? ›

Here are a few tips to consider when taking an internet speed test.
  1. Test using both Ethernet and Wi-Fi connections. A wired Ethernet connection is always faster than a wireless connection. ...
  2. Test at varying distances from your router. ...
  3. Test using different devices. ...
  4. Test at different times of the day.
Sep 1, 2024

How fast should a unit test run? ›

Running unit tests should be fast, the sooner an issue is found the quicker the turnaround time. On average, a unit test should take a few milliseconds to run. This will help ensure that tests are narrow and isolated.

How to speed up UI tests? ›

One of the simplest ways to speed up your testing is to run more tests — specifically, by running tests in parallel. You can increase speed and efficiency with parallel testing while expanding your testing coverage. Plus, you can use it to create a shorter feedback loop and get results faster and more often.

What are the three A's of unit testing? ›

A unit test typically features three different phases: Arrange, Act, and Assert (sometimes referred to as AAA).

What should be avoided in unit testing? ›

5 Common Unit Testing Mistakes
  • Not Securing the Perimeter. Bugs can creep into the crevices of your system, finding gaps in your defense. ...
  • Test Bundling. ...
  • False Positives. ...
  • Stopping Short. ...
  • Testing Nothing.
Oct 30, 2023

What is the best way to study for a unit test? ›

Follow these study tips to make your best grade!
  1. Get informed. Don't walk into your test unprepared for what you will face. ...
  2. Think like your teacher. ...
  3. Make your own study aids. ...
  4. Practice for the inevitable. ...
  5. Study every day. ...
  6. Cut out the distractions. ...
  7. Divide big concepts from smaller details. ...
  8. Don't neglect the “easy” stuff.

Why is unit testing so hard? ›

Unit testing is hard because it deals with things at a very granular level. It puts your code under the microscope and forces you to test in tiny increments.

How to structure unit testing? ›

The AAA pattern, which stands for Arrange, Act, and Assert, is a widely adopted structure for organizing unit tests. It provides a clear and readable format for writing tests: Arrange: Set up the necessary preconditions and inputs for the test. Act: Execute the unit of code being tested.

What is a unit testing strategy? ›

Unit testing is a software testing technique where individual components or units of a software application are tested in isolation to ensure they perform as expected. It involves testing each unit of the code independently to verify its functionality, typically using automated testing frameworks.

How can I make my speed test higher? ›

Before you contact your internet service provider (ISP) or mobile carrier, check to see if you're running any ongoing downloads or other programs like video chat that might be hogging your bandwidth. Close those and test again. If your Speedtest result still seems slow, reboot your phone or computer, modem and router.

How do I get better at unit tests? ›

Follow Arrange, Act, Assert

The AAA is a general approach to writing more readable unit tests. In the first step, you arrange things up for testing. It's where you set variables, instantiate objects, and do the rest of the required setup for the test to run. During this step, you also define the expected result.

How can I get faster at taking tests? ›

Seven Best Test-Taking Tips for Success
  1. Listen to the Instructions. ...
  2. Read the Entire Test. ...
  3. Do a “Brain Dump” ...
  4. Answer the Questions You Know First. ...
  5. Answer the Questions You Skipped. ...
  6. Be Sure the Test is Complete. ...
  7. Check Your Work.
Sep 29, 2022

How can I run faster for a test? ›

How to Run Faster: 22 Expert Tips
  1. Test Out a Quicker Pace. One of the first steps in learning how to run faster is to feel what it's like to pick up the pace. ...
  2. Run More Often. ...
  3. Work on Your Form. ...
  4. Count Your Strides. ...
  5. Develop Your Anaerobic Threshold. ...
  6. Do Speed Work. ...
  7. Practice Fartleks. ...
  8. Incorporate Hill Training.
Apr 30, 2024

References

Top Articles
Latest Posts
Recommended Articles
Article information

Author: Patricia Veum II

Last Updated:

Views: 5433

Rating: 4.3 / 5 (64 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Patricia Veum II

Birthday: 1994-12-16

Address: 2064 Little Summit, Goldieton, MS 97651-0862

Phone: +6873952696715

Job: Principal Officer

Hobby: Rafting, Cabaret, Candle making, Jigsaw puzzles, Inline skating, Magic, Graffiti

Introduction: My name is Patricia Veum II, I am a vast, combative, smiling, famous, inexpensive, zealous, sparkling person who loves writing and wants to share my knowledge and understanding with you.