- Testy jednostkowe (Unit Tests)
Sprawa chyba jasna - testują najmniejszą "jednostkę" kodu: funkcję, metodę, klasę. Kolaboratorów naszej jednostki często mockujemy / patchujemy. Np. testy funkcji walidujących.
classTestFramesListValidation(TestCase):
deftest_that_list_of_ints_is_valid(self):
try:
validate_frames([1, 2])
exceptException: # pylint: disable=broad-exceptself.fail()
deftest_that_if_frames_is_not_a_list_of_ints_method_should_raise_exception(self):
withself.assertRaises(FrameNumberValidationError):
validate_frames({'1': 1})
withself.assertRaises(FrameNumberValidationError):
validate_frames((1, 2))- Testy jednostkowe+, testy jenostkowe-integracyjne, testy Django-integracyjne, testy komponentowe (Component Tests)
Nie wiem jak je dobrze nazwać. Być może wszystkie nazwy są dobre, bo dotyczą trochę różnych przypadków, dla których cechą wspólną jest to, że są ponad zwykłymi testami jednostkowymi, ale nie są to jeszcze typowe testy integracyjne. Nadal używa się w nich mocków i patchy, ale często usługi są faktycznie stawiane (nasłuchują na otwartych portach), zapisują coś-odczytują z bazy danych, strzelają prawdziwymi zapytaniami http, itd. Jako przykład podałbym testy MiddleMana lub SigningService-u.
classTestMiddleManServer:
@pytest.fixture(autouse=True)defsetUp(self, unused_tcp_port_factory, event_loop):
golem_message_frame=GolemMessageFrame(Ping(), 777).serialize(CONCENT_PRIVATE_KEY)
self.patcher=mock.patch("middleman.middleman_server.crash_logger")
self.crash_logger_mock=self.patcher.start()
self.internal_port, self.external_port=unused_tcp_port_factory(), unused_tcp_port_factory()
self.data_to_send=append_frame_separator(escape_encode_raw_message(golem_message_frame))
self.timeout=0.2self.short_delay=0.1self.middleman=MiddleMan(internal_port=self.internal_port, external_port=self.external_port, loop=event_loop)
yieldself.internal_port, self.external_portself.patcher.stop()
deftest_that_if_keyboard_interrupt_is_raised_application_will_exit_without_errors(self):
withpytest.raises(SystemExit) asexception_wrapper:
withmock.patch.object(self.middleman, "_run_forever", side_effect=KeyboardInterrupt):
self.middleman.run()
assert_that(exception_wrapper.value.code).is_equal_to(None)
self.crash_logger_mock.assert_not_called()
deftest_that_broken_connection_from_concent_is_reported_to_sentry(self):
withoverride_settings(
CONCENT_PRIVATE_KEY=CONCENT_PRIVATE_KEY,
CONCENT_PUBLIC_KEY=CONCENT_PUBLIC_KEY,
SIGNING_SERVICE_PUBLIC_KEY=SIGNING_SERVICE_PUBLIC_KEY,
):
schedule_sigterm(delay=self.timeout)
fake_client=socket.socket()
client_thread=get_client_thread(send_data, fake_client, self.data_to_send, self.internal_port, self.short_delay)
client_thread.start()
error_message="Connection_error"withmock.patch(
"middleman.middleman_server.request_producer",
new=async_stream_actor_mock(side_effect=Exception(error_message))
):
withpytest.raises(SystemExit):
self.middleman.run()
client_thread.join(self.timeout)
fake_client.close()
self.crash_logger_mock.error.assert_called_once()
assert_that(self.crash_logger_mock.error.mock_calls[0][1][0]).contains(error_message)lub Djangowe testy dla handlerów:
@override_settings(CONCENT_PRIVATE_KEY=CONCENT_PRIVATE_KEY,CONCENT_PUBLIC_KEY=CONCENT_PUBLIC_KEY,CONCENT_MESSAGING_TIME=10, # secondsFORCE_ACCEPTANCE_TIME=10, # secondsCONCENT_ETHEREUM_PUBLIC_KEY='x'*ETHEREUM_PUBLIC_KEY_LENGTH,)classAcceptOrRejectIntegrationTest(ConcentIntegrationTestCase):
defsetUp(self):
super().setUp()
self.patcher=mock.patch('core.message_handlers.calculate_subtask_verification_time', return_value=10)
self.addCleanup(self.patcher.stop)
self.patcher.start()
deftest_provider_forces_subtask_results_for_task_which_was_already_submitted_concent_should_refuse(self):
""" Tests if on provider ForceSubtaskResults message Concent will return ServiceRefused if ForceSubtaskResults with same task_id was already submitted. Expected message exchange: Provider -> Concent: ForceSubtaskResults Concent -> Provider: HTTP 202 Provider -> Concent: ForceSubtaskResults Concent -> Provider: ServiceRefused """task_to_compute=self._get_deserialized_task_to_compute(
timestamp="2018-02-05 10:00:00",
deadline="2018-02-05 10:00:15",
)
# STEP 1: Provider forces subtask results via Concent.# Request is processed correctly.serialized_force_subtask_results=self._get_serialized_force_subtask_results(
timestamp="2018-02-05 10:00:30",
ack_report_computed_task=self._get_deserialized_ack_report_computed_task(
timestamp="2018-02-05 10:00:20",
task_to_compute=task_to_compute,
signer_private_key=self.REQUESTOR_PRIVATE_KEY,
)
)
withmock.patch(
'core.message_handlers.payments_service.is_account_status_positive',
side_effect=_get_provider_account_status_true_mock
) asis_account_status_positive_true_mock_function:
withfreeze_time("2018-02-05 10:00:30"):
response_1=self.client.post(
reverse('core:send'),
data=serialized_force_subtask_results,
content_type='application/octet-stream',
)
is_account_status_positive_true_mock_function.assert_called_with(
client_eth_address=task_to_compute.requestor_ethereum_address,
pending_value=task_to_compute.price,
)
assertlen(response_1.content) ==0assertresponse_1.status_code==202self._assert_stored_message_counter_increased(increased_by=3)
self._test_subtask_state(
task_id=task_to_compute.task_id,
subtask_id=task_to_compute.subtask_id,
subtask_state=Subtask.SubtaskState.FORCING_ACCEPTANCE,
provider_key=self._get_encoded_provider_public_key(),
requestor_key=self._get_encoded_requestor_public_key(),
expected_nested_messages={'task_to_compute', 'report_computed_task', 'ack_report_computed_task'},
next_deadline=parse_iso_date_to_timestamp("2018-02-05 10:00:45"),
)
self._test_last_stored_messages(
expected_messages=[
message.TaskToCompute,
message.ReportComputedTask,
message.tasks.AckReportComputedTask,
],
task_id=task_to_compute.task_id,
subtask_id=task_to_compute.subtask_id,
)
self._test_undelivered_pending_responses(
subtask_id=task_to_compute.subtask_id,
client_public_key=self._get_encoded_requestor_public_key(),
expected_pending_responses_receive=[
PendingResponse.ResponseType.ForceSubtaskResults,
]
)
# STEP 2: Provider again forces subtask results via Concent with message with the same task_id.# Request is refused.withmock.patch(
'core.message_handlers.payments_service.is_account_status_positive',
side_effect=self.is_account_status_positive_true_mock
):
withfreeze_time("2018-02-05 10:00:31"):
response_2=self.client.post(
reverse('core:send'),
data=serialized_force_subtask_results,
content_type='application/octet-stream',
)
self._test_response(
response_2,
status=200,
key=self.PROVIDER_PRIVATE_KEY,
message_type=message.concents.ServiceRefused,
fields= {
'reason': message.concents.ServiceRefused.REASON.DuplicateRequest,
'timestamp': parse_iso_date_to_timestamp("2018-02-05 10:00:31"),
}
)
self._assert_stored_message_counter_not_increased()
self._assert_client_count_is_equal(2)Testy integracyjne (Integration Tests)
Testują współdziałanie/przepływ informacji/dopasowanie interfejsów dla dwóch lub więcej komponentów. Nie używamy w nich już mocków i patchy - uruchamiamy prawdziwe usługi, itp. Niekiedy korzystamy z tzw. "Fake-ów" czyli klientów/klas, itp. z ograniczoną przyciętą funkcjonalnością, za pomocą których "podłączamy" się do testowanych komponentów i inicjujemy przepływ. W testach integracyjnych możemy zaglądać w "bebechy" systemu, tj. wnioskować o tym czy testy przeszły czy nie na podstawie danych normalnie nie dostępnych z zewnątrz.
Istniejących przykładów jest raczej niewiele, np test z signing_service_integration_test_middleman.
Testy E2E (End-to-End) (End-to-End Tests or E2E Tests)
Testy przeprowadzane niejako z perspeltywy klienta - testują cały system jako "czarną skrzynkę". Używamy zatem tych samych API/endpointów (punktów wejścia-wyjścia) do sytemu, z których korzystałby klient przy normalnym użytkowaniu. Nie mamy dostęþu do "bebechów" systemu - całe wnioskowanie o tym czy funkcjonalność działa jak należy opiera się o dane z "oficjalnego" wyjścia. Oczywiście, oznacza to, że nie używamy w nich mocków, patchy, itd.
Przykładem byłyby nasze cluster testy, np. api-e2e-additional-verification-test.
Sprawa chyba jasna - testują najmniejszą "jednostkę" kodu: funkcję, metodę, klasę. Kolaboratorów naszej jednostki często mockujemy / patchujemy. Np. testy funkcji walidujących.
Nie wiem jak je dobrze nazwać. Być może wszystkie nazwy są dobre, bo dotyczą trochę różnych przypadków, dla których cechą wspólną jest to, że są ponad zwykłymi testami jednostkowymi, ale nie są to jeszcze typowe testy integracyjne. Nadal używa się w nich mocków i patchy, ale często usługi są faktycznie stawiane (nasłuchują na otwartych portach), zapisują coś-odczytują z bazy danych, strzelają prawdziwymi zapytaniami http, itd. Jako przykład podałbym testy MiddleMana lub SigningService-u.
lub Djangowe testy dla handlerów:
Testy integracyjne (Integration Tests)
Testują współdziałanie/przepływ informacji/dopasowanie interfejsów dla dwóch lub więcej komponentów. Nie używamy w nich już mocków i patchy - uruchamiamy prawdziwe usługi, itp. Niekiedy korzystamy z tzw. "Fake-ów" czyli klientów/klas, itp. z ograniczoną przyciętą funkcjonalnością, za pomocą których "podłączamy" się do testowanych komponentów i inicjujemy przepływ. W testach integracyjnych możemy zaglądać w "bebechy" systemu, tj. wnioskować o tym czy testy przeszły czy nie na podstawie danych normalnie nie dostępnych z zewnątrz.
Istniejących przykładów jest raczej niewiele, np test z
signing_service_integration_test_middleman.Testy E2E (End-to-End) (End-to-End Tests or E2E Tests)
Testy przeprowadzane niejako z perspeltywy klienta - testują cały system jako "czarną skrzynkę". Używamy zatem tych samych API/endpointów (punktów wejścia-wyjścia) do sytemu, z których korzystałby klient przy normalnym użytkowaniu. Nie mamy dostęþu do "bebechów" systemu - całe wnioskowanie o tym czy funkcjonalność działa jak należy opiera się o dane z "oficjalnego" wyjścia. Oczywiście, oznacza to, że nie używamy w nich mocków, patchy, itd.
Przykładem byłyby nasze cluster testy, np.
api-e2e-additional-verification-test.