Running tests and linting¶
Your business logic should of course be covered by unit tests. When
generating code using lime-project generate
the result
includes boilerplate tests, where you can add your own tests. For Python
we are using pytest and our own pytest
plugin lime-test
.
For Web Components we are using Stencils test framework
In a Lime Project we are also using linting tools to catch common code mistakes and follow a shared set of formatting rules. In Python we are using flake8 and for Web Components we are using tslint
Info
Our standard CI toolchain will fail any build containing any test or lint error.
Python¶
Running Python tests¶
To run Python tests run
poetry run pytest .
You can also configure VSCode to discover and run Python tests.
Linting and formatting Python¶
To lint your Python code:
poetry run flake8 .
You can also configure VSCode to automatically lint your code
We are also including autopep8
as an Python formatter, which will automatically format your code. You can configure VSCode to do this for you.
The lime-test
framework¶
lime-test
helps you write tests for code that makes use of the Lime CRM platform, such as plugins or non-core packages. It's implemented as a plugin to pytest, which gives you a set of ready made fixtures that you can use for the more basic types of tests for Lime. For more control, you are encouraged to use the available helper functions that allows you to create more pointed test code.
The following example uses the core_app fixture, which loads a complete LimeApplication <lime-application:lime_application.application.LimeApplication>
with a set of lime types defined that closely matches the current state of the lime-core database. It also loads a complete database to memory that is disregarded after the test ends to ensure that multiple tests don't interfere with each other.
The code we want to test:
def average(limeobjects, propname):
values = [o.get_property(propname).value for o in limeobjects]
return sum(values) / len(values)
def test_average(core_app):
Deal = core_app.limetypes.deal
deals = [
Deal(value=2),
Deal(value=4)
]
assert average(deals, 'value') == 3
Fixtures¶
lime-test
is implemented as a plugin to pytest, which means that as soon as it's installed, it will contain fixtures to be used to set up the conditions for your tests more quickly. These fixtures are more often than not to be considered a starting point, and you'll most likely find yourself in need of more fine grained control over your test setups.
To use a fixture in your tests, do this:
def test_my_app(core_app):
uow = core_app.unit_of_work()
uow.add(core_app.limetypes.company(name='acme'))
uow.commit()
assert len(list(core_app.limetypes.company.get_all())) == 1
Defining data structures¶
In order for your tests to be manageable, it's essential to distinctly be able to define and read what types are expected of your code. To help with this, lime-test makes use of lime-core's ability to express data structures as dicts or YAML-documents.
The following shows an example of a fixture that sets up an empty database with a coworker and a company that relates to each other:
@pytest.fixture
def my_database(empty_database):
types = """
company:
name: string
rating:
type: option
options:
- poor
- good
- excellent
responsible:
type: belongsto
related: coworker
backref: companies
coworker:
company:
type: hasmany
related: company
backref: responsible
"""
limetypes = lime_type.create_limetypes_from_dsl(types)
lime_test.db.add_limetypes(empty_database, limetypes)
return empty_database
lime_type.create_limetypes_from_dsl
. It's also possible to define object instances to use in your tests by using lime_type.create_limeobjects_from_dsl
.
In-memory Web Application¶
lime-test
enables you to start up a complete web application in memory that you can make requests to to ensure that your endpoints work as they should. It builds on the in memory database to create a complete environment for your endpoints that is created for your specific test method, and teared down immediately afterwards.
It's an implementation of Flasks's test server
A simple example¶
Given the following endpoint:
class DealAverage(webserver.LimeResource):
def get(self, companyid):
company = self.application.limetypes.company.get(companyid)
avg = averagelib.average(company.properties.deal.fetch(), 'value')
return {'average': avg}
api.add_resource(DealAverage, '/dealaverage/<int:companyid>')
We can write the following test:
def test_calculate_average_for_deals(webapp, limeapp):
Company = limeapp.limetypes.company
Deal = limeapp.limetypes.deal
uow = limeapp.unit_of_work()
acme = Company(name='acme')
acme_idx = uow.add(acme)
deals = [
Deal(name='deal1', value=42),
Deal(name='deal2', value=128),
]
for d in deals:
acme.properties.deal.attach(d)
uow.add(d)
res = uow.commit()
acme = res[acme_idx]
res = webapp.get('/myapp/acme_solution/dealaverage/{acme.id}'
.format(acme=acme))
json_response = json.loads(res.data.decode('utf-8'))
assert res.status_code == 200
assert json_response == {'average': 85.0}