Smart contracts are immutable programs: Once a contract is deployed, it cannot be altered. This allows users to be sure that the rules by which their funds are operated will not be changed. However, the same feature makes creating secure smart contracts extremely complicated. If you create a contract with a bug or vulnerability, it is there forever. That’s why testing is even more crucial for smart contracts than for traditional applications.
Why testing is necessary
First, let’s clarify what problems tests can and cannot solve. For this purpose, let’s point out the following difference between bugs and vulnerabilities:
→ If an issue leads to a planned scenario not running, it is a bug.
→ If an issue leads to an unplanned scenario running, it is a vulnerability.
Tests do not prevent vulnerabilities. A vulnerability is by definition something unplanned, so you can’t take it into account at the testing stage. To deal with vulnerabilities, you need other tools and actions, which are described at the end of this article.
Tests help us make sure that all planned scenarios run as intended. In other words, tests help prevent bugs. This fact has several important consequences:
- Rule of thumb: Every line of business logic must have a corresponding test. If you have a scenario in business logic, then you’ll have it in the code, which means you need to test it.
- The most important scenarios must be tested the most thoroughly. The scenarios that will be run by most users or that implement critical functionality require additional attention.
- Tests catch silly mistakes. Silly mistakes are made even by experienced developers and can be very dangerous.
- Tests are great for edge cases. What if a user tries to buy zero tokens? What if the number of users reaches the limit? These cases must be considered, and the best solution for this task is using tests.
- You need to have a detailed specification for your project to create tests. Let us discuss this point in more detail.
You cannot test a planned scenario if you don’t know what is planned. So, you need to create a specification.
This seems obvious, but many blockchain teams break this rule. They write a white paper, and after that, they immediately write code. This can and often does lead to incorrect implementation of the desired functionality. At some point, the team can even come to the conclusion that they themselves don’t understand how they want the system to behave. Moreover, the desired functionality might be not self-consistent and thus is unimplementable.
So, a specification is a must. This leads us to an interesting observation: When you start testing your code, you don’t just get tests. You have to improve your whole development procedure. You work more than you first expected, but you also benefit more.
Tools for testing
Testing is a whole separate branch of knowledge, so I am not going to cover it in one article. However, I will name the first necessary tools.
To test your smart contracts easily and correctly, you will need a testing framework. My colleagues recommend using one of the following: Truffle, Embark or Etherlime.
Also, you will need to measure test coverage. Test coverage is a percentage of the code that is covered by tests, so 100% coverage means that every bytecode instruction is tested. However, this is an ideal situation, not a real one. In practice, this number will always be below 100, depending on how many tests you create. Here are some of the most popular tools to measure test coverage: solidity-coverage and @0x/sol-coverage.
Expanding the context
So, if test coverage is 95% and I have tests for all the critical functions, I can consider my code secure, right? But the correct answer is, “Wrong!” Testing is only one of the necessary security procedures. Take a look at this diagram:
When you start writing your code, you first need to use a linter to make the code clear and readable. Then you need tests to make sure that all the planned scenarios work correctly. Then you run security tools to find standard and easily detectable vulnerabilities. After you remove them from the code, you can proceed to the external audit of the code. At every stage of development, the code design will be crucial for security as well as for usability.
All of these steps have their complications, but those are beyond the scope of this article.
The views, thoughts and opinions expressed here are the author’s alone and do not necessarily reflect or represent the views and opinions of Cointelegraph.