Hari ini kami membuka kode sumber untuk testsuite, kerangka kerja untuk menguji layanan HTTP, yang dikembangkan dan digunakan oleh Yandex.Taxi. Sumber diterbitkan di GitHub di bawah lisensi MIT.Dengan testuite, mudah untuk menguji layanan HTTP. Ini menyediakan mekanisme siap pakai untuk:Backend Yandex.Taxi terdiri dari ratusan layanan microser, yang baru terus muncul. Kami mengembangkan semua layanan yang sangat dimuat dalam C ++ menggunakan kerangka kerja pengguna kami sendiri, kami sudah membicarakannya di Habré . Kami membuat layanan dan prototipe yang lebih ringan dengan Python.Untuk memastikan bahwa layanan menyelesaikan masalahnya dengan baik, menyediakan API ke layanan lain dan aplikasi akhir, kami ingin mengujinya secara keseluruhan, terutama berdasarkan prinsip kotak hitam.Tidak ada alat yang siap pakai untuk ini - Anda harus menulis kode untuk mengatur lingkungan pengujian, yang akan menjadi:- meningkatkan dan mengisi basis data;
- mencegat dan memalsukan permintaan HTTP;
- menjalankan layanan pengujian di lingkungan ini.
Memecahkan masalah ini menggunakan kerangka kerja untuk pengujian unit terlalu sulit dan salah, karena tugas mereka berbeda: pengujian unit unit struktural yang lebih kecil - komponen, kelas, fungsi.Testsuite didasarkan pada pytest , kerangka uji Python standar. Tidak masalah apa bahasa dari layanan microser yang kami uji ini ditulis. Sekarang testuite berjalan pada GNU / Linux, sistem operasi macOS.Meskipun testuite cocok untuk skenario integrasi, yaitu interaksi beberapa layanan (dan jika layanan ditulis dengan Python, maka untuk yang tingkat rendah), kami tidak akan mempertimbangkan kasus ini. Lebih lanjut, kami hanya akan fokus pada pengujian satu layanan.Prinsip operasi
Tujuan utamanya adalah untuk memastikan bahwa layanan menjawab panggilan HTTP dengan benar, jadi kami menguji melalui panggilan HTTP.Memulai / menghentikan layanan adalah operasi rutin. Karena itu, kami memeriksa:- bahwa setelah memulai layanan merespons melalui HTTP;
- bagaimana layanan berperilaku jika layanan eksternal sementara tidak tersedia.

Testsuite:- Mulai basis data (PostgreSQL, MongoDB ...).
- Sebelum setiap tes, ini mengisi database dengan data uji.
- Mulai layanan mikro yang diuji dalam proses terpisah.
- Meluncurkan server webnya sendiri (mockserver), yang meniru (mengering) lingkungan eksternal untuk layanan.
- Melakukan tes.
Tes dapat memeriksa:- Apakah layanan menangani permintaan HTTP dengan benar.
- Bagaimana layanan ini bekerja secara langsung dalam database.
- Ada / tidaknya / urutan panggilan ke layanan eksternal.
- Keadaan internal layanan menggunakan informasi yang diteruskan ke Testpoint.
mockserver
Kami menguji perilaku satu layanan mikro. Panggilan ke API HTTP layanan eksternal harus dijembatani. Untuk bagian pekerjaan ini di testuite, temui plug-in mockserver
dan mockserver_https
. Mockserver adalah server HTTP dengan penangan permintaan yang dapat disesuaikan untuk setiap tes dan memori tentang permintaan mana yang diproses dan data apa yang ditransfer.Basis data
Testsuite memungkinkan tes untuk secara langsung mengakses database untuk membaca dan menulis. Dengan menggunakan data, Anda dapat merumuskan prasyarat untuk pengujian dan memeriksa hasilnya. Out of the box mendukung PostgreSQL, MongoDB, Redis.Cara mulai menggunakan
Untuk menulis tes testuite, pengembang harus mengetahui Python dan kerangka pytest standar .Mari kita tunjukkan menggunakan testuite langkah demi langkah menggunakan contoh obrolan sederhana. Berikut adalah kode sumber untuk aplikasi dan tes.
Frontend chat.html berinteraksi dengan layanan chat-backend .Untuk menunjukkan bagaimana layanan berinteraksi, chat-backend mendelegasikan penyimpanan pesan ke layanan repositori. Penyimpanan diimplementasikan dalam dua cara, chat-storage-mongo dan chat-storage-postgres .obrolan backend
Layanan chat-backend adalah titik masuk untuk permintaan dari front-end. Mampu mengirim dan mengembalikan daftar pesan.Layanan
Kami menunjukkan contoh penangan permintaan POST /messages/retrieve
:Kode sumber@routes.post('/messages/retrieve')
async def handle_list(request):
async with aiohttp.ClientSession() as session:
response = await session.post(
storage_service_url + 'messages/retrieve',
timeout=HTTP_TIMEOUT,
)
response.raise_for_status()
response_body = await response.json()
messages = list(reversed(response_body['messages']))
result = {'messages': messages}
return web.json_response(result)
Tes
Persiapkan infrastruktur testuite untuk peluncuran layanan. Kami menunjukkan dengan pengaturan apa kami ingin memulai layanan.Sumber
@pytest.fixture(scope='session')
async def service_daemon(
register_daemon_scope, service_spawner, mockserver_info,
):
python_path = os.getenv('PYTHON3', 'python3')
service_path = pathlib.Path(__file__).parent.parent
async with register_daemon_scope(
name='chat-backend',
spawn=service_spawner(
[
python_path,
str(service_path.joinpath('server.py')),
'--storage-service-url',
mockserver_info.base_url + 'storage/',
],
check_url=SERVICE_BASEURL + 'ping',
),
) as scope:
yield scope
Kami mengatur fixture klien, melalui itu tes mengirimkan permintaan HTTP ke layanan.Sumber@pytest.fixture
async def server_client(
service_daemon,
service_client_options,
ensure_daemon_started,
mockserver,
):
await ensure_daemon_started(service_daemon)
yield service_client.Client(SERVICE_BASEURL, **service_client_options)
Sekarang infrastruktur tahu bagaimana memulainya chat-backend
dan bagaimana cara mengirim permintaan. Ini cukup untuk memulai tes menulis.Harap dicatat bahwa dalam pengujian chat-backend
kami tidak menggunakan layanan penyimpanan, baik chat-storage-mongo
, maupun chat-storage-postgres
. Untuk chat-backend
menangani panggilan secara normal, kami membasahi API penyimpanan mockserver
.Mari kita menulis tes untuk metode ini POST messages/send
. Kami memverifikasi bahwa:- permintaan diproses secara normal;
- saat memproses permintaan,
chat-backend
panggil metode penyimpanan POST messages/send
.
Sumberasync def test_messages_send(server_client, mockserver):
@mockserver.handler('/storage/messages/send')
async def handle_send(request):
assert request.json == {
'username': 'Bob',
'text': 'Hello, my name is Bob!',
}
return mockserver.make_response(status=204)
response = await server_client.post(
'messages/send',
json={'username': 'Bob', 'text': 'Hello, my name is Bob!'},
)
assert response.status == 204
assert handle_send.times_called == 1
Mari kita menulis tes untuk metode ini POST messages/retrieve
. Kami memverifikasi bahwa:- permintaan diproses secara normal;
- saat memproses permintaan,
chat-backend
panggil metode penyimpanan POST /messages/retrieve
; chat-backend
“Membalik” daftar pesan yang diterima dari repositori sehingga pesan-pesan terbaru ada di akhir daftar.
Sumberasync def test_messages_retrieve(server_client, mockserver):
messages = [
{
'username': 'Bob',
'created': '2020-01-01T12:01:00.000',
'text': 'Hi, my name is Bob!',
},
{
'username': 'Alice',
'created': {'$date': '2020-01-01T12:02:00.000'},
'text': 'Hi Bob!',
},
]
@mockserver.json_handler('/storage/messages/retrieve')
async def handle_retrieve(request):
return {'messages': messages}
response = await server_client.post('messages/retrieve')
assert response.status == 200
body = response.json()
assert body == {'messages': list(reversed(messages))}
assert handle_retrieve.times_called == 1
chat-storage-postgres
Layanan chat-storage-postgres
ini bertanggung jawab untuk membaca dan menulis pesan obrolan ke database PostgreSQL.Layanan
Ini adalah bagaimana kami membaca daftar pesan dari PostgreSQL dalam metode POST /messages/retrieve
:Source Code@routes.post('/messages/retrieve')
async def get(request):
async with app['pool'].acquire() as connection:
records = await connection.fetch(
'SELECT created, username, "text" FROM messages '
'ORDER BY created DESC LIMIT 20',
)
messages = [
{
'created': record[0].isoformat(),
'username': record[1],
'text': record[2],
}
for record in records
]
return web.json_response({'messages': messages})
Tes
Layanan yang kami uji menggunakan database PostgreSQL. Agar semuanya berfungsi, yang perlu Anda lakukan adalah memberi tahu testsuite di direktori mana untuk mencari skema tabel.Sumber@pytest.fixture(scope='session')
def pgsql_local(pgsql_local_create):
tests_dir = pathlib.Path(__file__).parent
sqldata_path = tests_dir.joinpath('../schemas/postgresql')
databases = discover.find_databases('chat_storage_postgres', sqldata_path)
return pgsql_local_create(list(databases.values()))
Pengaturan
infrastruktur conftest.py lainnya tidak berbeda dengan layanan yang dijelaskan di atas chat-backend
.Mari kita beralih ke tes.Mari kita menulis tes untuk metode ini POST messages/send
. Periksa apakah itu menyimpan pesan ke database.Sumberasync def test_messages_send(server_client, pgsql):
response = await server_client.post(
'/messages/send', json={'username': 'foo', 'text': 'bar'},
)
assert response.status_code == 200
data = response.json()
assert 'id' in data
cursor = pgsql['chat_messages'].cursor()
cursor.execute(
'SELECT username, text FROM messages WHERE id = %s', (data['id'],),
)
record = cursor.fetchone()
assert record == ('foo', 'bar')
Mari kita menulis tes untuk metode ini POST messages/retrieve
. Periksa apakah ia mengembalikan pesan dari database.Pertama, buat skrip yang menambahkan catatan yang kita butuhkan ke tabel. Testsuite akan secara otomatis menjalankan skrip sebelum pengujian.Sumber
INSERT INTO messages(id, created, username, text) VALUES (DEFAULT, '2020-01-01 00:00:00.0+03', 'foo', 'hello, world!');
INSERT INTO messages(id, created, username, text) VALUES (DEFAULT, '2020-01-01 00:00:01.0+03', 'bar', 'happy ny');
Sumber
async def test_messages_retrieve(server_client, pgsql):
response = await server_client.post('/messages/retrieve', json={})
assert response.json() == {
'messages': [
{
'created': '2019-12-31T21:00:01+00:00',
'text': 'happy ny',
'username': 'bar',
},
{
'created': '2019-12-31T21:00:00+00:00',
'text': 'hello, world!',
'username': 'foo',
},
],
}
Meluncurkan
Contoh menjalankan paling mudah dalam wadah buruh pelabuhan. Untuk melakukan ini, Anda perlu membuat docker dan docker-compose yang diinstal pada mesin.Semua contoh diluncurkan dari direktori Mulai obrolan .docs/examples
docs/examples$ make run-chat-mongo
docs/examples$ make run-chat-postgres
Setelah peluncuran, URL akan ditampilkan di konsol, tempat Anda dapat membuka obrolan di browser:chat-postgres_1 | ======== Running on http://0.0.0.0:8081 ========
chat-postgres_1 | (Press CTRL+C to quit)
Jalankan tes
docs/examples$ make docker-runtests
docs/examples$ make docker-runtests-mockserver-example
docs/examples$ make docker-runtests-mongo-example
docs/examples$ make docker-runtests-postgres-example
Dokumentasi
Dokumentasi testuite terperinci tersedia di sini .Petunjuk untuk menyiapkan dan menjalankan contoh.Jika Anda memiliki pertanyaan github.com/yandex/yandex-taxi-testsuite/issues - tinggalkan komentar.