Files
OpenBB/tests/README.md
Chavithra 0c205cbeae Tests : Stock > Prediction Techniques + Doc (#1060)
* Updating tests : conftest + pytest.ini

* Updating tests : stocks/prediction_techniques

* Updating tests : doc
2021-12-16 15:11:55 +01:00

415 lines
11 KiB
Markdown
Vendored

# 1. Gamestonk Terminal : `Unit Testing`
This document is part of the `Gamestonk Terminal` library documentation.
It aims to provide necessary information in order :
- Run `unit tests`
- Build `unit tests`
- Maintain `unit tests`
## 1.1. Why having unit tests ?
Here `Unit tests` purpose is to allow : update without fear.
Insuring with enough confidence that : addition, removal or update of a `module/function` won't break other parts of the code.
To do that we need `unit tests` to be :
- fast to write
- fast to run
**FAST TO WRITE**
The `unit tests` will only tests the most common cases.
**FAST TO RUN**
Slow part of the code like `network` or `database` access should be either `mocked` or `marked` as `slow`.
# 2. Run `unit tests`
In this section we will explain every think you need to run the `unit tests` on `Gamestonk Terminal`.
## 2.1. How to install tests dependencies ?
To run the tests you will need the `dev-dependencies`.
By default poetry installs the `dev-dependencies` when you run this command :
```bash
poetry install
```
If you dont want to install the `dev-dependencies` you will have to add the option `--no-dev` like this :
```bash
poetry install --no-dev
```
## 2.2. How to run `tests` : by `module` ?
You can run tests on a specific package/module by specifying the path of this package/module, like this :
```bash
pytest tests/gamestonk_terminal/some_package
pytest tests/gamestonk_terminal/some_package/some_module.py
```
## 2.3. How to run `tests` : by `name` ?
You can run tests by their name :
```
pytest -k "test_function1"
```
## 2.4. How to run `tests` : by `markers` ?
You can run tests only on specific markers, like this :
```
pytest -m slow
pytest -k "not slow"
pytest -k "not slow and not network"
```
You can list the available markers using this command :
```
pytest --markers
```
## 2.5. How to skip tests function ?
```python
import pytest
@pytest.skip
def test_some_function(mocker):
pass
@pytest.skip("This time with a comment")
def test_another_function(mocker):
pass
```
## 2.6. How to skip tests modules ?
```python
import pytest
pytest.skip(msg="Some optional comment.", allow_module_level=True)
def test_some_function(mocker):
pass
```
# 3. Build `unit tests`
## 3.1. Where to add my `test module` ?
All the `unit tests` should be insides the `tests` folder.
There should be at most one `test module` for each `module` of `Gamestonk Terminal`.
Each `test module` should follow the same path than the `module` it is `testing`.
For instance to `test` the following module :
- `gamestonk_terminal/stocks/due_diligence/dd_controller.py`
A `test module` should be added here :
- `tests/gamestonk_terminal/stocks/due_diligence/test_dd_controller.py`
## 3.2. How to record network ?
Network exchange can be recording inside a `test function` using the `@pytest.mark.vcr` `marker`.
Here is an example :
```python
@pytest.mark.vcr
def test_single():
assert requests.get("http://httpbin.org/get").text == '{"get": true}'
```
You can choose the `cassette` name using the `@pytest.mark.default_cassette` marker.
Here is an example :
```python
@pytest.mark.default_cassette("example.yaml")
@pytest.mark.vcr
def test_default():
assert requests.get("http://httpbin.org/get").text == '{"get": true}'
```
## 3.2. Where to put data files ?
Each data file should be related exactly to one `test module`.
The purpose is to have each `test module` as self-contained as possible.
Data related to a `test module` should be stored within the same folder than this `test module`.
Example with `tests/gamestonk_terminal/some_package/some_module.py` :
- `tests/gamestonk_terminal/some_package`/cassettes/`some_module`/test_function1.yaml
- `tests/gamestonk_terminal/some_package`/txt/`some_module`/test_function1.csv
- `tests/gamestonk_terminal/some_package`/csv/`some_module`/test_function1.csv
- `tests/gamestonk_terminal/some_package`/json/`some_module`/test_function1.json
## 3.3. How to filter `api keys` on `cassettes` ?
The filtering of `api keys` from a `cassette` should be done directly inside the `test module` using this `cassette`.
The purpose is to have each `test module` as self-contained as possible.
To filter `api keys` from a `cassettes` you can define a `vcr_config` fixture inside your `test module`.
Here is an example of filtering :
```python
@pytest.fixture(scope="module")
def vcr_config():
return {
"filter_query_parameters": [
("token", "MOCK_TOKEN"),
]
}
```
More information about `vcr_config` fixture in the documentation of `pytest-recording` :
- https://github.com/kiwicom/pytest-recording
## 3.4. How to `mock` ?
**PYTEST-MOCK**
A `mocker` fixture is available through the package `pytest-mock`.
Here is an example on how to `mocker` fixture can be used :
```python
def test_something(mocker):
mocker.patch("some_package.some_module")
```
More information about `pytest-mock` are available here :
- https://pypi.org/project/pytest-mock/
**MOCKEYPATCH**
There is also a `monkeypatch` fixture available by default inside `pytest`.
```python
def test_double(monkeypatch):
monkeypatch.setattr("some_package.some_module", "Some value")
```
More information about `monkeypatch` are available here :
- https://docs.pytest.org/en/6.2.x/monkeypatch.html
## 3.5. Which `helpers` are available ?
You can find the available helpers inside the following package/module :
- `tests/helpers/`
- `tests/conftest.py`
See also the `pytest fixtures` which are autoloaded helpers.
## 3.6. Which `fixtures` are available ?
You can list all the available `pytest fixtures` using the following command :
```
pytest --fixtures
```
More on custom fixtures here :
- [FIXTURES](FIXTURES.md)
## 3.7. Which `markers` are available ?
You can list the available markers using this command :
```
pytest --markers
```
More information on markers location are available in pytest documentation :
- https://docs.pytest.org/en/6.2.x/mark.html#mark
## 3.8. Known `issue` / `solution`
**YFINANCE**
If a method is using the `yfinance` library but do not let you pick the `start/end` dates it will pick the current date each time.
So your test might fail the next days.
To solve that you can filter the `start/end` date like this :
```python
@pytest.fixture(scope="module")
def vcr_config():
return {
"filter_query_parameters": [
("period1", "1598220000"),
("period2", "1635980400"),
]
}
```
You can also refactor this method to let access to `stard/end` dates.
**USER-AGENT**
Some function uses a random `User-Agent` on the `HTTP HEADER` when fetching data from an `API`.
Here is how to filter this random `User-Agent`.
```python
@pytest.fixture(scope="module")
def vcr_config():
return {
"filter_headers": [("User-Agent", None)],
}
```
**YFINANCE**
If you do something like this with `yfinance` library.
```python
import yfinance as yf
yf.download(tickers="VCYT QSI")
```
Chances are you requests will be multi-threaded.
Issues : as for now `vcrpy` seems to be incompatible with multi-threading.
The library `vcrpy` is used to record `cassettes` (`network` calls into `yaml` files).
Here is a solution to still combine `yfinance` and `vcrpy` :
```python
import pytest
import yfinance
yf_download = yf.download
def mock_yf_download(*args, **kwargs):
kwargs["threads"] = False
return yf_download(*args, **kwargs)
@pytest.mark.vcr
def test_ark_orders_view(kwargs_dict, mocker, use_color):
mocker.patch("yfinance.download", side_effect=mock_yf_download)
yf.download(tickers="VCYT QSI")
```
## 3.9. List of useful `vscode` tools for `unit tests`
**VSCODE TESTING**
The default testing tool from `vscode` should let you add breakpoints and run debug on a specific `test`.
It's a convenient way to see what's inside your `test` while running.
More information on this tool are available here :
- https://code.visualstudio.com/docs/python/testing
## 3.10. How to handle `dev-dependencies` ?
**UPDATE PYPROJECT**
If a library need to be added or removed from `dev-dependencies` this can be done directly in the `pyproject.toml` file.
All the `dev-dependencies` should be under the `tool.poetry.dev-dependencies` section, like in this example :
```toml
[tool.poetry.dev-dependencies]
pytest = "^6.2.2"
pylint = "^2.7.2"
flake8 = "^3.9.0"
...
```
**EXPORT REQUIREMENTS**
After updating the `pyproject.toml` you will have to export the `requirements` files using the following commands :
```bash
poetry export -f requirements.txt -o requirements.txt --without-hashes --dev
poetry export -f requirements.txt -o requirements-full.txt --extras prediction --without-hashes --dev
```
# 4. Maintain `unit tests`
## 4.1. What is the PR process ? (in progress)
Here are the steps to write proper tests for Gamestonk :
A. Find right place
B. Verify coverage is above 90%
C. Set the right markers
**A. Find right place**
Put the code following the same module and package structure than `gamestonk_terminal` package.
**B. Verify coverage is above 90%**
Once you made your `PullRequest` an automation will let you know whether or not you have the proper amount of tests coverage.
You can also run the following command to check your coverage manually :
- `pytest --cov --cov-fail-under=90`
**C. Set the right markers**
If parts of your have specificities like :
- being `slow`
- requiring `network` connectivity
- requiring `authentication` to `APIs`
Then the proper markers should be sent on each test.
The rests of this document is there to provide a deeper comprehension of this steps.
A tests update might be asked whenever the answer to one of the following question is `yes` :
A. Is this PR fixing bug which was not detected by the current tests ?
B. Is this PR reducing code coverage ?
C. Is this PR adding features ?
D. Is this PR updating features ?
## 4.2. Which automations are available ?
**PULL REQUEST**
A `github action` should comment every `Pull Request` with the code `coverage`.
This automation will not enforce any rules regarding `tests`.
## 4.3. How to check code coverage ?
**PULL REQUEST AUTOMATION**
You should be able to see the code coverage on comment of your `Pull Requests`.
**MANUALLY**
This is how to manually check code coverage.
```bash
pytest --cov=gamestonk_terminal --cov=terminal --cov-report term-missing
```
You can also select a specific package/module with the option `--cov`.
Here is an example where we select only the package `stocks\due_diligence` :
```bash
pytest --cov=gamestonk_terminal\stocks\due_diligence --cov-report term-missing
```
**PRE-COMMIT**
Code `coverage` and `unit tests` are not run on `pre-commit` hooks since it will slow down each commit.