-
Notifications
You must be signed in to change notification settings - Fork 16.3k
Add more precise tests (tests for jinja params) for AirflowVersion #8505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
kaxil
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it
|
I recreated the Extra Links PR: @mock.patch('airflow.www.views.dagbag.get_dag')
@mock.patch('airflow.www.views.BaseView.render_template')
def test_extra_link_in_gantt_view(self, mock_render_template, get_dag_function):
from tests.test_utils.mock_operators import Dummy2TestOperator
dag = DAG('ex_dag', start_date=self.default_date)
task = Dummy2TestOperator(task_id="some_dummy_task_2", dag=dag)
get_dag_function.return_value = dag
exec_date = dates.days_ago(2)
start_date = datetime(2020, 4, 10, 2, 0, 0)
end_date = exec_date + timedelta(seconds=30)
with create_session() as session:
for task in dag.tasks:
ti = TaskInstance(task=task, execution_date=exec_date, state="success")
ti.start_date = start_date
ti.end_date = end_date
session.add(ti)
with self.app.app_context():
mock_render_template.return_value = make_response("RESPONSE", 200)
url = 'gantt?dag_id={}&execution_date={}'.format(dag.dag_id, exec_date)
self.client.get(url, follow_redirects=True)
mock_render_template.assert_called_once_with(
'airflow/gantt.html',
base_date=mock.ANY, dag=mock.ANY,
data={
'taskNames': ['some_dummy_task_2'],
'tasks': [
{
'task_id': 'some_dummy_task_2',
'dag_id': 'ex_dag',
'execution_date': '2020-04-19T00:00:00+00:00',
'start_date': '2020-04-10T02:00:00+00:00', 'end_date': '2020-04-19T00:00:30+00:00',
'duration': None, 'state': 'success', 'try_number': 1,
'max_tries': 0, 'hostname': '', 'unixname': 'root',
'job_id': None, 'pool': 'default_pool', 'pool_slots': 1, 'queue': 'default',
'priority_weight': 1,
'operator': 'Dummy2TestOperator', 'queued_dttm': None,
'pid': None, 'executor_config': {},
'extraLinks': ['github', 'airflow']
}
], 'height': 50
},
demo_mode=False,
execution_date=mock.ANY, form=mock.ANY, root=mock.ANY, scheduler_job=mock.ANY
)arghh we will need to find a way to test individual args such that order of elements in list or dict doesn't matter. |
|
We can create context - class TestVersionView(TestBase):
def test_version(self):
with catch_jinja_params() as jinja_params:
resp = self.client.get('version', data=dict(
username='test',
password='test'
), follow_redirects=True)
self.check_content_in_response('Version Info', resp)
self.assertEqual(jinja_params.template_name, 'airflow/version.html')
self.assertEqual(jinja_params.params, dict(
airflow_version=version.version,
git_version=mock.ANY,
scheduler_job=mock.ANY,
title='Version Info'
))However, I only wanted to present the general concept in this PR. |
I like the idea. Without that we would have to do it like the following which works too but less ideal: @mock.patch('airflow.www.views.dagbag.get_dag')
@mock.patch('airflow.www.views.BaseView.render_template')
def test_extra_link_in_gantt_view(self, mock_render_template, get_dag_function):
from tests.test_utils.mock_operators import Dummy2TestOperator
dag = DAG('ex_dag', start_date=self.default_date)
Dummy2TestOperator(task_id="some_dummy_task_2", dag=dag)
get_dag_function.return_value = dag
exec_date = dates.days_ago(2)
start_date = datetime(2020, 4, 10, 2, 0, 0)
end_date = exec_date + timedelta(seconds=30)
with create_session() as session:
for task in dag.tasks:
ti = TaskInstance(task=task, execution_date=exec_date, state="success")
ti.start_date = start_date
ti.end_date = end_date
session.add(ti)
with self.app.app_context():
mock_render_template.return_value = make_response("RESPONSE", 200)
url = 'gantt?dag_id={}&execution_date={}'.format(dag.dag_id, exec_date)
self.client.get(url, follow_redirects=True, data=dict(
username='test',
password='test'
))
self.assertCountEqual(
mock_render_template.call_args[1]['data']['tasks'][0]['extraLinks'],
['airflow', 'github']
) |
|
Converted to draft just so that we can discuss, I definitely like the approach :) |
|
I aadded catch_jinja_params contextmanager. |
|
I like this, I tested it with: @mock.patch('airflow.www.views.dagbag.get_dag')
def test_extra_link_in_gantt_view(self, get_dag_function):
from tests.test_utils.mock_operators import Dummy2TestOperator
dag = DAG('dag', start_date=self.default_date)
Dummy2TestOperator(task_id="some_dummy_task_2", dag=dag)
get_dag_function.return_value = dag
exec_date = dates.days_ago(2)
start_date = datetime(2020, 4, 10, 2, 0, 0)
end_date = exec_date + timedelta(seconds=30)
with create_session() as session:
for task in dag.tasks:
ti = TaskInstance(task=task, execution_date=exec_date, state="success")
ti.start_date = start_date
ti.end_date = end_date
session.add(ti)
url = 'gantt?dag_id={}&execution_date={}'.format(self.dag.dag_id, exec_date)
with catch_jinja_params() as jinja_params:
self.client.get(url, follow_redirects=True)
self.assertEqual(jinja_params.template_name, 'airflow/gantt.html')
self.assertCountEqual(
jinja_params.template_params['data']['tasks'][0]['extraLinks'],
['airflow', 'github'])and works fine. However, I have 1 question: Do we lose any Airflow related args/customizations because of |
|
@kaxil We don't lose anything because we replace the method in the BaseView class with mock, and then mock calls the original method. As a result, we have a wrapped(transparent) method. If any class inherits from BaseView, it must use super () for mock to be called. Please note that |
turbaszek
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love the idea!
|
Love it as well. |
|
Will look in more detail soon, but I don't think mocking is the best approach here - check out https://stackoverflow.com/a/40531281 or flask-testing module |
|
Great feature though |
tests/test_utils/asserts.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And rather than a separate method/in utils, I would suggest making this a method on the already existing TestBase class -- after all it is only useful if you have a flask app, and TestBase sets that up for us.
|
@ashb we commonly use mock and this is a proven solution that we know well. However, I will look at this flask-testing. Thanks for the answer. |
|
Using the suggested answer from the SO post has some advantages:
@mock.patch('airflow.www.views.dagbag.get_dag')
@mock.patch('airflow.www.views.BaseView.render_template')
def test_extra_link_in_gantt_view(self, mock_render_template, get_dag_function):
from tests.test_utils.mock_operators import Dummy2TestOperator
dag = DAG('ex_dag', start_date=self.default_date)
task = Dummy2TestOperator(task_id="some_dummy_task_2", dag=dag)
get_dag_function.return_value = dag
exec_date = dates.days_ago(2)
start_date = datetime(2020, 4, 10, 2, 0, 0)
end_date = exec_date + timedelta(seconds=30)
with create_session() as session:
for task in dag.tasks:
ti = TaskInstance(task=task, execution_date=exec_date, state="success")
ti.start_date = start_date
ti.end_date = end_date
session.add(ti)
with captured_templates(self.app) as jinja_params:
url = 'gantt?dag_id={}&execution_date={}'.format(dag.dag_id, exec_date)
self.client.get(url, follow_redirects=True)
assert len(templates) == 1
template, context = templates[0]
assert template.name == 'index.html'
assert context['data'] == {
'taskNames': ['some_dummy_task_2'],
'tasks': [
{
'task_id': 'some_dummy_task_2',
'dag_id': 'ex_dag',
'execution_date': '2020-04-19T00:00:00+00:00',
'start_date': '2020-04-10T02:00:00+00:00', 'end_date': '2020-04-19T00:00:30+00:00',
'duration': None, 'state': 'success', 'try_number': 1,
'max_tries': 0, 'hostname': '', 'unixname': 'root',
'job_id': None, 'pool': 'default_pool', 'pool_slots': 1, 'queue': 'default',
'priority_weight': 1,
'operator': 'Dummy2TestOperator', 'queued_dttm': None,
'pid': None, 'executor_config': {},
'extraLinks': ['github', 'airflow']
}
], 'height': 50
}
)(untested though) |
| ############################################################################################################ | ||
| devel = [ | ||
| 'beautifulsoup4~=4.7.1', | ||
| 'blinker', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
@potiuk @ashb @kaxil @turbaszek I have updated PR. Now I use flask.signals instead of mock. |
ashb
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I think that is a bit cleaner.
Got idea on removing the "global" context vars too
|
@potiuk Do you have any comments? I rewrote the whole PR from scratch, so you can change your opinions. |
|
Looks great! |
Recently, I talked to @kaxil about testing our views. I suggested that we should test templates and flask views separately I have prepared a POC that shows how it can be done. I think this can increase confidence in these tests. We are currently testing only random words, but very much logic has no coverage and it is very difficult to make changes. If you want to make changes, you always have to test everything manually to see if the logic is behaving correctly. If we have automatic tests with a higher level of confidence then the changes will be easier to review and we will trust them more.
Make sure to mark the boxes below before creating PR: [x]
In case of fundamental code change, Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in UPDATING.md.
Read the Pull Request Guidelines for more information.