An In-Depth Guide to Testing Ethereum Smart Contracts

This article is part of a series. If you haven’t yet, check out the previous articles:

Part One: Why we Test
Part Two: Core Concepts of Testing
Part Three: Writing Basic Tests
Part Four: Running Your Tests
Part Five: Tools and Techniques for Effective Testing
Part Six: Parametrization and Property-Based Testing
Part Seven: Stateful Testing

Before we get into actually writing tests, it’s good to review some core testing concepts. We’re going to focus on unit testing initially — integration testing follows many of the same principles, but it is a more complex and nuanced subject that is better approached once we have a handle on unit tests.

We begin with a definition. What is a unit test?

“A unit test is a test that verifies a single behavior or component within your code.”

Properties of Unit Tests

There are many properties that set a good unit test apart from a bad one. Try to keep all of these in mind as you write your own tests.

  • Simple: Each unit test should be evaluating a single behavior. When a test fails it should be immediately obvious why. It is better to write many short tests with a single assertion, than one long test with many assertions.
  • Repeatable: A unit test should always produce the same result, as long as the code being tested has not changed. Tests should never be influenced by some uncontrollable parameter. This is important so that when a test fails, you can repeat it and observe the result to help you find the issue.
  • Isolated: Tests should not depend upon or affect other tests You should be able to run your tests in any order, together or independently, and always receive the same outcome.
  • Readable: Use long, descriptive variable names and don’t be afraid of adding comments to your tests. Once you have finished writing a test, it’s likely you’ll never look at that code again - until it fails. When that does happen, you should not have to spend time trying to understand what the test is doing.
  • Fast: Unit tests should be written with speed in mind. Each time you add or change your code you should run your tests to confirm that everything is still working. Having a quick test suite ensure you get this feedback sooner.

We’ll explore each of these ideas in greater depth throughout this tutorial.

Design Principles

When writing tests, there is a commonly referenced design principle known as Arrange-Act-Assert (AAA):

  • First, you arrange the initial conditions required
  • Next, you act by calling the function to be tested
  • Finally, you assert the result of the action

Following this pattern ensures that your tests are readable and not overly complex. If you find yourself performing many actions and assertions in a single test, or making an assertion mid-test and then following it with more actions — consider refactoring into multiple tests. Remember, we want our tests to be simple and easy to understand when they fail!

What to Test

Well… everything! Take the following example, a simple ERC20 transfer function written in Vyper:

Generally, we want to start by considering the possible paths through the function. In this case there are two outcomes:

  • The token transfer is successful. In this case, testable behaviors include the sender and receiver balances being correctly adjusted, the Transfer event firing and the function returning True.
  • The transfer is not successful. In this case, we expect that the transaction reverts with an error message of "Insufficient Funds" when the sender does not have a sufficient balance.

Going further we might also want to test edge cases, such as:

  • A transfer of 0 tokens
  • A transfer where the sender and receiver are the same address

In some cases we might also write tests to perform assertions about behaviors that should not occur, such as a change in the total supply.

So you can see, from this single function we can produce many unit tests!

What’s Next

In “Part Three: Writing Basic Tests”, we explore the basics of pytest and write our first tests!

You can also follow the Brownie Twitter account, read my other Medium articles, and join us on Gitter.

--

--

--

I like to buidl stuff. https://github.com/iamdefinitelyahuman/

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

AWS Redshift WLM(Work Load Management)

Track your assets with cloud-based, serverless technology

Step by Step Guide on Web Scraping Using Scrapy In Python

Linux Boot Procedure

Process of development

Why TF should I stub dependencies for an E2E test?

(Tutorial) CDP Ingestion API SalesForce Integration Process

Create a Wordpress instance on AWS Lightsail

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ben Hauser

Ben Hauser

I like to buidl stuff. https://github.com/iamdefinitelyahuman/

More from Medium

What is Solidity?

IERC721Receiver interface: migrating Solidity (Ethereum) contracts to TAKAMAKA.

Ownership Exploitation in Solidity

80/20 #4 — Building a notification app for BAYC transactions on ethereum