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 which uses Jest,
and some helpers from @lundalogik/limeclient.js
to help create an environment that is similar to a real application.
In Lime projects we are also using linting tools to catch common code mistakes and follow a shared set of formatting rules.
For Python we are using flake8 and for Web Components we are using ESLint.
Info
Our standard CI toolchain will fail any build containing any test or lint error.
Python¶
Running tests¶
To run your Python tests with pytest use:
VS Code can be configured to discover and run the tests.
Linting and formatting¶
flake8¶
To lint you Python code with flake8 use:
VS Code can be configured to automatically lint your code.
black¶
We also recommend formatting the code with black, which will format your code according to a set of formatting rules.
If you have black installed you can either use it to check your code for inconsistencies with regards to the formatting rules, or run the automatic formatting.
Checking your code:
Formatting your code:
isort¶
Another recommended formatting tool is isort, which will sort and format the import statements in your python files.
If you have isort installed it can also be used to check your code or run automatic formatting.
Checking your code:
Formatting your code:
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.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)
The code we can use to test it:
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. It is recommended to use a separate YAML file for the database structure so it can be reused for testing web components in the frontend.
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
@pytest.fixture
def my_database(empty_database):
with open("dsl.yaml", "r") as file:
types = file.read()
limetypes = lime_type.create_limetypes_from_dsl(types)
lime_test.db.add_limetypes(empty_database, limetypes)
return empty_database
For more detailed information see 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}