# Testing Cairo with Python

By [cairopractice](https://paragraph.com/@cairopractice) · 2022-09-29

---

Cairo lang ships with support for writing tests in Python. I think it’s in many ways superior to the other ways of testing Cairo code. In this post, I share some testing tips and techniques I hope others find useful as well.

pytest
------

Since the Cairo lang modules are in Python we can use [pytest](https://docs.pytest.org/en/latest/) to write out tests. I absolutely love pytest - it's, **by far**, the best testing framework I came across in any language. If you’re new to it, spend some time reading through the documentation, it will definitely pay off.

In the mean time, get ready to by pytest-pilled:

#### pytest.approx

pytest ships with [pytest.approx](https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest-approx), a function to tests for numerical precision. If you’re building a defi protocol, It's handy when dealing with all the wei rounding discrepancies.

#### pytest.raises

Another helpful built in is the [pytest.raises](https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest-raises) context manager which helps you to assert that a transaction failed. It even supports a `match` argument to evaluate the message conforms to an expected string. See an [example of this in action in the repo](https://github.com/milancermak/cairopractice/blob/master/tests/test_tips.py#L19-L31).

#### Interactive prompt on failure

You can run pytest with the `--pdb` flag. It will start a Python REPL when encountering an error. Unfortunately, it only works in context of Python, not the Cairo VM, but it’s still very helpful for debugging in certain cases. If you have `ipdb` installed, you can add `--pdbcls=IPython.terminal.debugger:Pdb` to use a much more convenient `ipython` REPL.

#### Running a single test case

To run only a single test case in a file, pass its name separated by double colons on the command line:

`pytest test/token/test_erc20.py::test_transfer`

This will run just the `test_transfer` test from the `test_erc20.py` file.

#### Running only matching tests

An extension of the above is the `-k` command line argument, which allows you to specify a string. pytest will then check this string against all the names of test functions and run only those that match. For example:

    pytest test/token/test_erc20.py -k transfer
    

This would execute `test_transfer`, `test_transfer_from` and `test_transfer_failures`.

There’s more advance stuff you could do with `-k`, have a look at the [docs](https://docs.pytest.org/en/latest/reference/reference.html#command-line-flags).

#### Command line arguments

There's a ton of [command line arguments](https://docs.pytest.org/en/latest/reference/reference.html#command-line-flags) that alter the behaviour of pytest. I pretty much always use `-sv` (`-s` is to see `print` outputs and `-v` to increase verbosity of the test suite run). Another useful one is `--lf` (short for `--last-failed`) which instructs pytest to execute only those tests that failed in the previous test run.

#### pytest.ini

You can put the commonly used cmdline args into a configuration file. This is the default `pytest.ini` I use for every new project:

    [pytest]
    addopts = -s
      --disable-warnings
      --pdbcls=IPython.terminal.debugger:Pdb
    

* * *

Cairo specific tips and utils
-----------------------------

Now for more tips geared towards testing Cairo contracts.

#### Using caller\_address

A lot of the test I see use a Signer class and `send_transaction` to emulate an account contract. Unless you're actually working on a wallet, this is completely unnecessary.

A better way is to use the `caller_address` argument of [execute](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/testing/contract.py#L289) to simulate a transaction being sent from an address of your choice. Check [this example in the repo](https://github.com/milancermak/cairopractice/blob/master/tests/test_tips.py#L34-L41). This technique will also speed up your tests, because you don't have to compile and deploy an account contract anymore.

#### Compile contracts for testing

The [compile\_starknet\_files](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/compiler/compile.py#L106-L121) function has two helpful flags you should set to true in a testing environment: `debug_info` and `disable_hint_validation`. It's also a good idea to apply the `@functools.cache` decorator on the compile function to speed up your test suite. [See the](https://github.com/milancermak/cairopractice/blob/master/tests/utils.py#L55-L65) `compile_contract` function in the repo.

#### Take advantage of hints

Thanks to compiling contracts with disabled hint validation, you can do pretty much anything you want inside a Cairo hint. It's a full blown Python environment. The most obvious thing is to `print` values - just remember that local variables live under the `ids` namespace inside a hint:

`%{ print("value of let foo is", ids.foo") %}`

If you’re running the test suite with `-s` as mentioned above, you’ll see the output of print from hints in the console.

#### Working with Cairo structs in Python

There’s two ways of how I deal with Cairo `sturct`s in Python.

Let’s say we have this struct declared in a Cairo contract:

    struct Person {
        age: felt,
        height: felt,
        countries_visited: felt
    }
    

I either create an equivalent `namedtuple` in Python:

    from collections import namedtuple
    
    Person = namedtuple("Person", "age height countries_visited")
    adventurer = Person(43, 77, 112)
    

Or I use the compiled contract to do the same:

    contract = compile_contract("person.cairo")
    adventurer = contract.Person(43, 77, 112)
    

Whichever way you use, they are easily comparable in Python. A single `assert` on the whole struct will do. As always, [there’s a full example in the repo](https://github.com/milancermak/cairopractice/blob/master/tests/test_tips.py#L44-L64).

#### Mock block number and block timestamp

In case you need to manipulate the block number or timestamp, you can [use this](https://github.com/milancermak/cairopractice/blob/master/tests/conftest.py#L19-L49) `block_info` fixture. Just pass it as an argument to your test and off you go.

#### Print transaction execution resources

A call to `execute` returns an instance of [StarknetCallInfo](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/testing/objects.py#L12) class. Use it to get the number of steps and builtins the transaction consumed:

    tx = await contract.function().execute()
    print(tx.call_info.execution_resources)
    
    # ExecutionResources(n_steps=40, builtin_instance_counter={'range_check_builtin': 4, 'bitwise_builtin': 1}, n_memory_holes=2)
    

You can plug in the [fee weights](https://docs.starknet.io/docs/Fees/fee-mechanism/#general-case) and build a simple transaction cost reporting tool with your test suite (note this does not take into account storage costs).

* * *

I’m always looking for cool ways how to use pytest and how to test Cairo contracts. If you have anything to share, I’ll be [happy to hear from you](https://twitter.com/cairopractice).

---

*Originally published on [cairopractice](https://paragraph.com/@cairopractice/testing-cairo-with-python)*
