Perpustakaan tiruan lainnya

Selamat siang Saya terlibat dalam otomatisasi uji. Seperti semua insinyur otomatisasi, saya memiliki seperangkat perpustakaan dan alat yang biasanya saya pilih untuk menulis tes. Tetapi secara berkala ada situasi di mana tidak ada perpustakaan yang dikenal dapat memecahkan masalah dengan risiko membuat autotest tidak stabil atau rapuh. Pada artikel ini, saya ingin memberi tahu Anda bagaimana tugas standar menggunakan mock'ov membuat saya menulis modul. Saya juga ingin membagikan keputusan saya dan mendengar umpan balik.


Aplikasi


Salah satu sektor yang diperlukan di sektor keuangan adalah audit. Data perlu diperiksa secara berkala (rekonsiliasi). Dalam hal ini, aplikasi yang saya uji muncul. Agar tidak berbicara tentang sesuatu yang abstrak, mari kita bayangkan bahwa tim kami sedang mengembangkan aplikasi untuk memproses aplikasi dari pengirim pesan instan. Untuk setiap aplikasi, acara yang sesuai harus dibuat di elasticsearch. Aplikasi verifikasi akan menjadi pemantauan kami agar aplikasi tidak dilewati.


Jadi, bayangkan kita memiliki sistem yang memiliki komponen berikut:


  1. Server konfigurasi. Untuk pengguna, ini adalah titik masuk tunggal di mana ia mengkonfigurasi tidak hanya aplikasi untuk verifikasi, tetapi juga komponen lain dari sistem.
  2. Aplikasi verifikasi.
  3. Data dari aplikasi memproses aplikasi yang disimpan di elasticsearch.
  4. Data referensi. Format data tergantung pada messenger yang dengannya aplikasi terintegrasi.

Tantangan


Pengujian otomasi dalam hal ini terlihat cukup mudah:


  1. Persiapan Lingkungan:
    • Elasticsearch diinstal dengan konfigurasi minimal (menggunakan msi dan baris perintah).
    • Aplikasi verifikasi diinstal.
  2. Eksekusi uji:
    • Aplikasi verifikasi dikonfigurasikan.
    • Elasticsearch diisi dengan data uji untuk tes yang sesuai (berapa banyak aplikasi yang diproses).
    • Aplikasi menerima data "referensi" dari messenger (berapa banyak aplikasi yang seharusnya sebenarnya).
    • Putusan yang dikeluarkan oleh aplikasi diperiksa: jumlah aplikasi yang berhasil diverifikasi, jumlah aplikasi yang hilang, dll.
  3. Membersihkan lingkungan.

Masalahnya adalah kita menguji pemantauan, tetapi untuk mengkonfigurasinya, kita memerlukan data dari server konfigurasi. Pertama, menginstal dan mengkonfigurasi server untuk setiap proses adalah operasi yang menghabiskan waktu (misalnya, memiliki basis sendiri). Kedua, saya ingin mengisolasi aplikasi untuk menyederhanakan pelokalan masalah saat menemukan cacat. Pada akhirnya, diputuskan untuk menggunakan mock.


Ini mungkin menimbulkan pertanyaan: "Jika kita masih mengejek server, mungkin kita tidak bisa menghabiskan waktu menginstal dan mengisi elasticsearch, tetapi ganti tiruan?". Tapi tetap saja, Anda harus selalu ingat bahwa penggunaan mock memberikan fleksibilitas, tetapi menambahkan kewajiban untuk memantau relevansi perilaku mock. Karena itu, saya menolak untuk mengganti elasticsearch: cukup mudah untuk menginstal dan mengisinya.


Mock pertama


Server mengirimkan konfigurasi ke GET permintaan dalam beberapa cara di / konfigurasi. Kami tertarik pada dua cara. Yang pertama adalah /configuration/data_cluster dengan konfigurasi cluster


 { "host": "127.0.0.1", "port": 443, "credentials": { "username": "user", "password": "pass" } } 

Yang kedua adalah /configuration/reconciliation dengan konfigurasi aplikasi pengeboran


 { "reconciliation_interval": 3600, "configuration_update_interval": 60, "source": { "address": "file:///c:/path", "credentials": { "username": "user", "password": "pass" } } } 

Kesulitannya adalah Anda harus dapat mengubah respons server selama pengujian atau di antara pengujian untuk menguji bagaimana aplikasi bereaksi terhadap perubahan konfigurasi, kata sandi salah, dll.


Jadi, mock dan alat statis untuk mock dalam unit test (mock, monkeypatch dari pytest, dll.) Tidak akan bekerja untuk kita. Saya menemukan perpustakaan pretenders yang menurut saya tepat untuk saya. Pretenders menyediakan kemampuan untuk membuat server HTTP dengan aturan yang menentukan bagaimana server akan menanggapi permintaan. Aturan disimpan dalam preset, yang memungkinkan isolasi tiruan untuk suite tes yang berbeda. Preset dapat dihapus dan diisi ulang, memungkinkan Anda memperbarui jawaban sesuai kebutuhan. Cukup menaikkan server itu sendiri satu kali selama persiapan lingkungan:


 python -m pretenders.server.server --host 127.0.0.1 --port 8000 

Dan dalam tes kita perlu menambahkan penggunaan klien. Dalam kasus paling sederhana, ketika jawaban benar-benar dikodekan dalam tes, itu mungkin terlihat seperti ini:


 import json import pytest from pretenders.client.http import HTTPMock from pretenders.common.constants import FOREVER @pytest.fixture def configuration_server_mock(request): mock = HTTPMock(host="127.0.0.1", port=8000, name="server") request.addfinalizer(mock.reset) return mock def test_something(configuration_server_mock): configuration_server_mock.when("GET /configuration/data_cluster").reply( headers={"Content-Type": "application/json"}, body=json.dumps({ "host": "127.0.0.1", "port": 443, "credentials": { "username": "user", "password": "pass", }, }), status=200, times=FOREVER, ) configuration_server_mock.when("GET /configuration/reconciliation").reply( headers={"Content-Type": "application/json"}, body=json.dumps({ "reconciliation_interval": 3600, "configuration_update_interval": 60, "source": { "address": "file:///c:/path", "credentials": { "username": "user", "password": "pass", }, }, }), status=200, times=FOREVER, ) # test application 

Tapi itu belum semuanya. Dengan fleksibilitasnya, pretenders memiliki dua keterbatasan yang harus diingat dan harus ditangani dalam kasus kami:


  1. Aturan tidak bisa dihapus satu per satu. Untuk mengubah jawaban, Anda harus menghapus seluruh pra-atur dan membuat ulang semua aturan lagi.
  2. Semua jalur yang digunakan dalam aturan relatif. Preset memiliki lintasan unik dari form / mockhttp / <preset_name>, dan lintasan ini adalah awalan umum untuk semua lintasan yang dibuat dalam aturan. Aplikasi yang diuji hanya menerima nama host, dan tidak bisa tahu tentang awalan.

Keterbatasan pertama sangat tidak menyenangkan, tetapi dapat diselesaikan dengan menulis modul yang merangkum pekerjaan dengan konfigurasi. Misalnya saja


 configuration.data_cluster.port = 443 

atau (untuk membuat permintaan pembaruan lebih jarang)


 data_cluster_config = get_default_data_cluster_config() data_cluster_config.port = 443 configuration.update_data_cluster(data_cluster_config) 

Enkapsulasi semacam itu memungkinkan kami memperbarui semua jalur hampir tanpa kesulitan. Anda juga dapat membuat preset individu untuk setiap titik akhir individu dan preset umum (utama), mengarahkan (melalui 307 atau 308) ke masing-masing. Maka Anda hanya dapat menghapus satu preset untuk memperbarui aturan.


Untuk menyingkirkan awalan, Anda dapat menggunakan pustaka mitmproxy . Ini adalah alat yang ampuh yang memungkinkan, antara lain, untuk mengarahkan permintaan. Kami akan menghapus awalan sebagai berikut:


 mitmdump --mode reverse:http://127.0.0.1:8000 --replacements :~http:^/:/mockhttp/server/ --listen-host 127.0.01 --listen-port 80 

Parameter dari perintah ini melakukan hal berikut:


  1. --listen-host 127.0.0.1 dan --listen-port 80 jelas. Mitmproxy menaikkan servernya, dan dengan parameter ini kami menentukan antarmuka dan port yang akan didengarkan server ini.
  2. --mode reverse:http://127.0.0.1:8000 berarti permintaan ke server mitproxy akan dialihkan ke http://127.0.0.1:8000 . Baca lebih lanjut di sini .
  3. --replacements :~http:^/:/mockhttp/server/ mendefinisikan templat dengan mana permintaan akan diubah. Ini terdiri dari tiga bagian: filter permintaan ( ~http untuk permintaan HTTP), template untuk mengubah ( ^/ untuk mengganti awal jalan), dan benar-benar mengganti ( /mockhttp/server ). Baca lebih lanjut di sini .

Dalam kasus kami, kami menambahkan mockhttp/server ke semua permintaan HTTP dan mengalihkannya ke http://127.0.0.1:8000 , i.e. ke server pretenders kami. Sebagai hasilnya, kami telah mencapai bahwa sekarang konfigurasi dapat diperoleh dengan permintaan GET ke http://127.0.0.1/configuration/data_cluster .


Secara umum, saya puas dengan desain dengan pretenders dan mitmproxy . Dengan kompleksitas yang jelas - setelah semua, 2 server bukan satu yang nyata - persiapan terdiri dari menginstal 2 paket dan mengeksekusi 2 perintah pada baris perintah untuk memulainya. Tidak semuanya begitu sederhana dalam mengelola mock, tetapi semua kompleksitas hanya ada di satu tempat (mengelola preset) dan diselesaikan dengan cukup sederhana dan andal. Namun, keadaan baru muncul dalam masalah yang membuat saya berpikir tentang solusi baru.


Mock kedua


Sampai saat itu, saya hampir tidak mengatakan dari mana data referensi berasal. Pembaca yang penuh perhatian mungkin memperhatikan bahwa dalam contoh di atas, jalur ke sistem file digunakan sebagai alamat sumber data. Dan itu benar-benar berfungsi seperti ini, tetapi hanya untuk salah satu vendor. Vendor lain menyediakan API untuk menerima aplikasi, dan di situlah timbul masalah. Sulit untuk meningkatkan API vendor selama pengujian, jadi saya berencana untuk menggantinya dengan tiruan sesuai dengan skema yang sama seperti sebelumnya. Tetapi untuk menerima aplikasi, permintaan formulir


 GET /application-history?page=2&size=5&start=1569148012&end=1569148446 

Ada 2 poin di sini. Pertama, beberapa opsi. Faktanya adalah bahwa parameter dapat ditentukan dalam urutan apa pun, yang sangat menyulitkan ekspresi reguler untuk aturan pretenders . Perlu juga diingat bahwa parameternya opsional, tetapi ini bukan masalah seperti urutan acak. Kedua, parameter terakhir (mulai dan akhir) menentukan interval waktu untuk memfilter pesanan. Dan masalah dalam hal ini adalah bahwa kita tidak dapat memprediksi terlebih dahulu interval mana (bukan besarnya, tetapi waktu mulai) yang akan digunakan oleh aplikasi untuk membentuk respon tiruan. Sederhananya, kita perlu tahu dan menggunakan nilai-nilai parameter untuk membentuk jawaban "masuk akal". β€œMasuk akal” dalam hal ini penting, misalnya, sehingga kami dapat menguji bahwa aplikasi melewati semua halaman pagination: jika kami menjawab semua permintaan dengan cara yang sama, maka kami tidak akan dapat menemukan cacat karena fakta bahwa hanya satu halaman dari lima yang diminta. .


Saya mencoba mencari solusi alternatif, tetapi pada akhirnya saya memutuskan untuk mencoba menulis sendiri. Jadi ada server yang longgar . Ini adalah aplikasi Flask di mana jalur dan respons dapat dikonfigurasikan setelah dimulai. Di luar kotak, ia tahu cara bekerja dengan aturan untuk tipe permintaan (GET, POST, dll.) Dan untuk path. Ini memungkinkan Anda untuk mengganti pretenders dan mitmproxy dalam tugas asli. Saya juga akan menunjukkan bagaimana itu dapat digunakan untuk membuat tiruan untuk API vendor.


Aplikasi membutuhkan 2 jalur utama:


  1. Titik akhir dasar. Ini adalah awalan yang sama yang akan digunakan untuk semua aturan yang dikonfigurasi.
  2. Titik akhir konfigurasi. Ini adalah awalan dari permintaan tersebut yang dengannya Anda dapat mengkonfigurasi server tiruan itu sendiri.

 python -m looseserver.default.server.run --host 127.0.0.1 --port 80 --base-endpoint / --configuration-endpoint /_mock_configuration/ 

Secara umum, yang terbaik adalah tidak mengkonfigurasi titik akhir basis dan titik akhir konfigurasi sehingga satu adalah induk dari yang lain. Kalau tidak, ada risiko bahwa jalur untuk konfigurasi dan untuk pengujian akan bertentangan. Titik akhir konfigurasi akan diutamakan, karena aturan Flask ditambahkan untuk konfigurasi lebih awal daripada untuk jalur dinamis. Dalam kasus kami, kami dapat menggunakan --base-endpoint /configuration/ jika kami tidak akan memasukkan vendor API dalam tiruan ini.


Versi paling sederhana dari tes tidak banyak berubah


 import json import pytest from looseserver.default.client.http import HTTPClient from looseserver.default.client.rule import PathRule from looseserver.default.client.response import FixedResponse @pytest.fixture def configuration_server_mock(request): class MockFactory: def __init__(self): self._client = HTTPClient(configuration_url="http://127.0.0.1/_mock_configuration/") self._rule_ids = [] def create_rule(self, path, json_response): rule = self._client.create_rule(PathRule(path=path)) self._rule_ids.append(rule.rule_id) response = FixedResponse( headers={"Content-Type": "application/json"}, status=200, body=json.dumps(json_response), ) self._client.set_response(rule_id=rule.rule_id, response=response) def _delete_rules(self): for rule_id in self._rule_ids: self._client.remove_rule(rule_id=rule_id) mock = MockFactory() request.addfinalizer(mock._delete_rules) return mock def test_something(configuration_server_mock): configuration_server_mock.create_rule( path="configuration/data_cluster", json_response={ "host": "127.0.0.1", "port": 443, "credentials": { "username": "user", "password": "pass", }, } ) configuration_server_mock.create_rule( path="configuration/reconciliation", json_response={ "reconciliation_interval": 3600, "configuration_update_interval": 60, "source": { "address": "file:///applications", "credentials": { "username": "user", "password": "pass", }, }, } ) 

Fixture menjadi lebih sulit, tetapi aturan sekarang dapat dihapus satu per satu, yang menyederhanakan pekerjaan dengannya. Menggunakan mitmproxy tidak lagi diperlukan.


Mari kita kembali ke API vendor. Kami akan membuat jenis aturan baru untuk server longgar, yang, tergantung pada nilai parameter, akan memberikan jawaban yang berbeda. Selanjutnya kita akan menggunakan aturan ini untuk parameter halaman.


Aturan dan jawaban baru perlu dibuat untuk server dan klien. Mari kita mulai dengan server:


 from looseserver.server.rule import ServerRule class ServerParameterRule(ServerRule): def __init__(self, parameter_name, parameter_value=None, rule_type="PARAMETER"): super(ServerParameterRule, self).__init__(rule_type=rule_type) self._parameter_name = parameter_name self._parameter_value = parameter_value def is_match_found(self, request): if self._parameter_value is None: return self._parameter_name not in request.args return request.args.get(self._parameter_name) == self._parameter_value 

Setiap aturan harus mendefinisikan metode is_match_found , yang menentukan apakah itu harus bekerja untuk permintaan yang diberikan atau tidak. Parameter input untuk itu adalah objek permintaan. Setelah aturan baru dibuat, perlu "mengajar" server untuk menerimanya dari klien. Untuk melakukan ini, gunakan RuleFactory :


 from looseserver.default.server.rule import create_rule_factory from looseserver.default.server.application import configure_application def _create_application(base_endpoint, configuration_endpoint): server_rule_factory = create_rule_factory(base_endpoint) def _parse_param_rule(rule_type, parameters): return ServerParameterRule( rule_type=rule_type, parameter_name=parameters["parameter_name"], parameter_value=parameters["parameter_value"], ) server_rule_factory.register_rule( rule_type="PARAMETER", parser=_parse_param_rule, serializer=lambda rule_type, rule: None, ) return configure_application( rule_factory=server_rule_factory, base_endpoint=base_endpoint, configuration_endpoint=configuration_endpoint, ) if __name__ == "__main__": application = _create_application(base_endpoint="/", configuration_endpoint="/_mock_configuration") application.run(host="127.0.0.1", port=80) 

Di sini kami membuat pabrik untuk aturan secara default, sehingga berisi aturan yang kami gunakan sebelumnya, dan mendaftarkan tipe baru. Dalam hal ini, klien tidak memerlukan informasi aturan, sehingga serializer sebenarnya tidak melakukan apa pun. Selanjutnya pabrik ini ditransfer ke aplikasi. Dan itu sudah bisa dijalankan seperti aplikasi Flask biasa.


Situasi dengan klien serupa: kami membuat aturan dan pabrik. Tetapi untuk klien, pertama, tidak perlu mendefinisikan metode is_match_found , dan kedua, serializer dalam hal ini diperlukan untuk mengirim aturan ke server.


 from looseserver.client.rule import ClientRule from looseserver.default.client.rule import create_rule_factory class ClientParameterRule(ClientRule): def __init__(self, parameter_name, parameter_value=None, rule_type="PARAMETER", rule_id=None): super(ClientParameterRule, self).__init__(rule_type=rule_type, rule_id=rule_id) self.parameter_name = parameter_name self.parameter_value = parameter_value def _create_client(configuration_url): def _serialize_param_rule(rule): return { "parameter_name": rule.parameter_name, "parameter_value": rule.parameter_value, } client_rule_factory = create_rule_factory() client_rule_factory.register_rule( rule_type="PARAMETER", parser=lambda rule_type, parameters: ClientParameterRule(rule_type=rule_type, parameter_name=None), serializer=_serialize_param_rule, ) return HTTPClient(configuration_url=configuration_url, rule_factory=client_rule_factory) 

Masih menggunakan _create_client untuk membuat klien, dan aturan dapat digunakan dalam pengujian. Dalam contoh di bawah ini, saya menambahkan penggunaan aturan default lain: CompositeRule . Ini memungkinkan Anda untuk menggabungkan beberapa aturan menjadi satu sehingga hanya berfungsi jika masing-masing mengembalikan True untuk memanggil is_match_found .


 @pytest.fixture def configuration_server_mock(request): class MockFactory: def __init__(self): self._client = _create_client("http://127.0.0.1/_mock_configuration/") self._rule_ids = [] def create_paged_rule(self, path, page, json_response): rule_prototype = CompositeRule( children=[ PathRule(path=path), ClientParameterRule(parameter_name="page", parameter_value=page), ] ) rule = self._client.create_rule(rule_prototype) self._rule_ids.append(rule.rule_id) response = FixedResponse( headers={"Content-Type": "application/json"}, status=200, body=json.dumps(json_response), ) self._client.set_response(rule_id=rule.rule_id, response=response) ... mock = MockFactory() request.addfinalizer(mock._delete_rules) return mock def test_something(configuration_server_mock): ... configuration_server_mock.create_paged_rule( path="application-history", page=None, json_response=["1", "2", "3"], ) configuration_server_mock.create_paged_rule( path="application-history", page="1", json_response=["1", "2", "3"], ) configuration_server_mock.create_paged_rule( path="application-history", page="2", json_response=["4", "5"], ) 

Kesimpulan


mitmproxy pretenders dan mitmproxy menyediakan alat yang cukup kuat dan fleksibel untuk membuat tiruan. Keuntungannya:


  1. Pengaturan mudah.
  2. Kemampuan untuk mengisolasi set kueri menggunakan preset.
  3. Menghapus seluruh set yang terisolasi sekaligus.

Dengan kontra meliputi:


  1. Kebutuhan untuk membuat ekspresi reguler untuk aturan.
  2. Ketidakmampuan untuk mengubah aturan secara individual.
  3. Kehadiran awalan untuk semua jalur yang dibuat atau penggunaan pengalihan menggunakan mitmproxy .

Tautan dokumentasi:
Pretenders
Mitmproxy
Server longgar

Source: https://habr.com/ru/post/id476904/


All Articles