Runtime: Cara sederhana untuk mengejek metode httpClient.GetAsync(..) untuk pengujian unit?

Dibuat pada 4 Mei 2015  ·  157Komentar  ·  Sumber: dotnet/runtime

System.Net.Http sekarang telah diupload ke repo :smile: :tada: :balloon:

Setiap kali saya menggunakan ini di beberapa layanan, ini berfungsi dengan baik tetapi membuatnya sulit untuk pengujian unit => pengujian unit saya tidak ingin benar-benar mencapai titik akhir yang sebenarnya.

Bertahun-tahun yang lalu, saya bertanya kepada @davidfowl apa yang harus kita lakukan? Saya berharap saya memparafrasekan dan tidak salah mengutipnya - tetapi dia menyarankan agar saya memalsukan penangan pesan (yaitu HttpClientHandler ), menyambungkannya, dll.

Karena itu, saya akhirnya membuat pustaka pembantu bernama HttpClient.Helpers untuk membantu saya menjalankan pengujian unit saya.

Jadi ini berhasil ... tapi rasanya _sangat_ berantakan dan .. rumit. Saya yakin saya bukan orang pertama yang perlu memastikan pengujian unit saya tidak melakukan panggilan nyata ke layanan eksternal.

Apakah ada cara yang lebih mudah? Seperti .. tidak bisakah kita memiliki antarmuka IHttpClient dan kita dapat memasukkannya ke dalam layanan kita?

Design Discussion area-System.Net.Http test enhancement

Komentar yang paling membantu

Hai @SidharthNabar - Terima kasih telah membaca masalah saya, saya juga sangat menghargainya.

Buat kelas Handler baru

Kamu baru saja menjawab pertanyaanku :)

Itu tarian besar untuk digoyangkan juga, hanya untuk meminta kode asli saya untuk tidak _memukul jaringan_.

Saya bahkan membuat paket repo dan nuget HttpClient.Helpers .. hanya untuk mempermudah pengujian! Skenario seperti jalur Happy atau pengecualian yang dilemparkan oleh titik akhir jaringan ...

Itulah masalahnya -> tidak bisakah kita melakukan semua ini dan ... hanya metode tiruan saja?

Saya akan mencoba dan menjelaskan melalui kode..

Sasaran: Mengunduh sesuatu dari jalinan.

public async Foo GetSomethingFromTheInternetAsync()
{
    ....
    using (var httpClient = new HttpClient())
    {
        html = await httpClient.GetStringAsync("http://www.google.com.au");
    }
    ....
}

Mari kita lihat dua contoh:

Diberikan antarmuka (jika ada):

public interface IHttpClient
{
    Task<string> GetStringAsync(string requestUri);    
}

Kode saya sekarang dapat terlihat seperti ini ...

public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _httpClient ?? new HttpClient()) // <-- CHANGE dotnet/corefx#1
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

dan kelas tes

public async Task GivenAValidEndPoint_GetSomethingFromTheInternetAsync_ReturnsSomeData()
{
    // Create the Mock : FakeItEasy > Moq.
    var httpClient = A.Fake<IHttpClient>();

    // Define what the mock returns.
    A.CallTo(()=>httpClient.GetStringAsync("http://www.google.com.au")).Returns("some html here");

    // Inject the mock.
    var service = new SomeService(httpClient);
    ...
}

Ya! selesai.

Ok, sekarang dengan cara saat ini...

  1. buat kelas Handler baru - ya, kelas!
  2. Suntikkan pawang ke dalam layanan
public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _handler == null 
                                    ? new HttpClient()
                                    : new HttpClient(handler))
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

Tetapi rasa sakitnya sekarang adalah saya harus membuat kelas. Anda sekarang telah menyakiti saya.

public class FakeHttpMessageHandler : HttpClientHandler
{
 ...
}

Dan kelas ini dimulai dengan cukup sederhana. Sampai saya memiliki beberapa GetAsync calls (jadi saya perlu menyediakan beberapa contoh Handler?) -atau- beberapa httpClients dalam satu layanan. Juga, apakah kita Dispose dari handler atau menggunakannya kembali jika kita melakukan beberapa panggilan di blok logika yang sama (yang merupakan opsi ctor)?

misalnya.

public async Foo GetSomethingFromTheInternetAsync()
{
    string[] results;
    using (var httpClient = new HttpClient())
    {
        var task1 = httpClient.GetStringAsync("http://www.google.com.au");
        var task2 = httpClient.GetStringAsync("http://www.microsoft.com.au");

        results = Task.WhenAll(task1, task2).Result;
    }
    ....
}

ini bisa jadi lebih mudah dengan ini..

var httpClient = A.Fake<IHttpClient>();
A.CallTo(() = >httpClient.GetStringAsync("http://www.google.com.au"))
    .Returns("gooz was here");
A.CallTo(() = >httpClient.GetStringAsync("http://www.microsoft.com.au"))
    .Returns("ms was here");

bersih bersih bersih :)

Lalu - ada bagian berikutnya: Discoverability

Ketika saya pertama kali mulai menggunakan MS.Net.Http.HttpClient apinya cukup jelas :+1: Ok - dapatkan string dan lakukan secara asyncly.. simple ...

... tapi kemudian saya melakukan pengujian .... dan sekarang saya harus belajar tentang HttpClientHandlers ? Kenapa? Saya merasa bahwa ini semua harus disembunyikan di balik selimut dan saya tidak perlu khawatir tentang semua detail implementasi ini. Ini terlalu banyak! Anda mengatakan bahwa saya harus mulai melihat ke dalam kotak dan mempelajari beberapa pipa ledeng ... yang menyakitkan :cry:

Itu semua membuat ini lebih kompleks dari yang seharusnya, IMO.


Bantu saya Microsoft - Anda satu-satunya harapan saya.

Semua 157 komentar

HttpClient tidak lebih dari lapisan abstraksi di atas perpustakaan HTTP lain. Salah satu tujuan utamanya adalah memungkinkan Anda untuk mengganti perilaku (implementasi), termasuk namun tidak terbatas pada kemampuan untuk membuat pengujian unit deterministik.

Dalam karya lain, HttpClient itu sendiri berfungsi sebagai objek "nyata" dan "tiruan", dan HttpMessageHandler adalah apa yang Anda pilih untuk memenuhi kebutuhan kode Anda.

Tetapi jika terasa begitu banyak pekerjaan yang diperlukan untuk mengatur penangan pesan ketika (rasanya) ini dapat ditangani dengan antarmuka yang bagus? Apakah saya benar-benar salah paham dengan solusinya?

@PureKrome - terima kasih telah membahas ini untuk diskusi. Bisakah Anda menjelaskan apa yang Anda maksud dengan "begitu banyak pekerjaan yang diperlukan untuk mengatur penangan pesan"?

Salah satu cara untuk menguji unit HttpClient tanpa menyentuh jaringan adalah -

  1. Buat kelas Handler baru (misalnya FooHandler) yang diturunkan dari HttpMessageHandler
  2. Terapkan metode SendAsync sesuai kebutuhan Anda - jangan tekan jaringan/log permintaan-tanggapan/dll.
  3. Berikan instance FooHandler ini ke konstruktor HttpClient:
    var handler = baru FooHandler();
    var klien = new HttpClient(penangan);

Objek HttpClient Anda kemudian akan menggunakan Handler Anda alih-alih HttpClientHandler bawaan.

Terima kasih,
Sid

Hai @SidharthNabar - Terima kasih telah membaca masalah saya, saya juga sangat menghargainya.

Buat kelas Handler baru

Kamu baru saja menjawab pertanyaanku :)

Itu tarian besar untuk digoyangkan juga, hanya untuk meminta kode asli saya untuk tidak _memukul jaringan_.

Saya bahkan membuat paket repo dan nuget HttpClient.Helpers .. hanya untuk mempermudah pengujian! Skenario seperti jalur Happy atau pengecualian yang dilemparkan oleh titik akhir jaringan ...

Itulah masalahnya -> tidak bisakah kita melakukan semua ini dan ... hanya metode tiruan saja?

Saya akan mencoba dan menjelaskan melalui kode..

Sasaran: Mengunduh sesuatu dari jalinan.

public async Foo GetSomethingFromTheInternetAsync()
{
    ....
    using (var httpClient = new HttpClient())
    {
        html = await httpClient.GetStringAsync("http://www.google.com.au");
    }
    ....
}

Mari kita lihat dua contoh:

Diberikan antarmuka (jika ada):

public interface IHttpClient
{
    Task<string> GetStringAsync(string requestUri);    
}

Kode saya sekarang dapat terlihat seperti ini ...

public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _httpClient ?? new HttpClient()) // <-- CHANGE dotnet/corefx#1
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

dan kelas tes

public async Task GivenAValidEndPoint_GetSomethingFromTheInternetAsync_ReturnsSomeData()
{
    // Create the Mock : FakeItEasy > Moq.
    var httpClient = A.Fake<IHttpClient>();

    // Define what the mock returns.
    A.CallTo(()=>httpClient.GetStringAsync("http://www.google.com.au")).Returns("some html here");

    // Inject the mock.
    var service = new SomeService(httpClient);
    ...
}

Ya! selesai.

Ok, sekarang dengan cara saat ini...

  1. buat kelas Handler baru - ya, kelas!
  2. Suntikkan pawang ke dalam layanan
public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _handler == null 
                                    ? new HttpClient()
                                    : new HttpClient(handler))
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

Tetapi rasa sakitnya sekarang adalah saya harus membuat kelas. Anda sekarang telah menyakiti saya.

public class FakeHttpMessageHandler : HttpClientHandler
{
 ...
}

Dan kelas ini dimulai dengan cukup sederhana. Sampai saya memiliki beberapa GetAsync calls (jadi saya perlu menyediakan beberapa contoh Handler?) -atau- beberapa httpClients dalam satu layanan. Juga, apakah kita Dispose dari handler atau menggunakannya kembali jika kita melakukan beberapa panggilan di blok logika yang sama (yang merupakan opsi ctor)?

misalnya.

public async Foo GetSomethingFromTheInternetAsync()
{
    string[] results;
    using (var httpClient = new HttpClient())
    {
        var task1 = httpClient.GetStringAsync("http://www.google.com.au");
        var task2 = httpClient.GetStringAsync("http://www.microsoft.com.au");

        results = Task.WhenAll(task1, task2).Result;
    }
    ....
}

ini bisa jadi lebih mudah dengan ini..

var httpClient = A.Fake<IHttpClient>();
A.CallTo(() = >httpClient.GetStringAsync("http://www.google.com.au"))
    .Returns("gooz was here");
A.CallTo(() = >httpClient.GetStringAsync("http://www.microsoft.com.au"))
    .Returns("ms was here");

bersih bersih bersih :)

Lalu - ada bagian berikutnya: Discoverability

Ketika saya pertama kali mulai menggunakan MS.Net.Http.HttpClient apinya cukup jelas :+1: Ok - dapatkan string dan lakukan secara asyncly.. simple ...

... tapi kemudian saya melakukan pengujian .... dan sekarang saya harus belajar tentang HttpClientHandlers ? Kenapa? Saya merasa bahwa ini semua harus disembunyikan di balik selimut dan saya tidak perlu khawatir tentang semua detail implementasi ini. Ini terlalu banyak! Anda mengatakan bahwa saya harus mulai melihat ke dalam kotak dan mempelajari beberapa pipa ledeng ... yang menyakitkan :cry:

Itu semua membuat ini lebih kompleks dari yang seharusnya, IMO.


Bantu saya Microsoft - Anda satu-satunya harapan saya.

Saya juga ingin melihat cara mudah dan sederhana untuk menguji berbagai hal yang menggunakan HttpClient. :+1:

Terima kasih atas tanggapan terperinci dan cuplikan kode - itu sangat membantu.

Pertama, saya perhatikan bahwa Anda membuat contoh baru HttpClient untuk mengirim setiap permintaan - itu bukan pola desain yang dimaksudkan untuk HttpClient. Membuat satu instans HttpClient dan menggunakannya kembali untuk semua permintaan Anda membantu mengoptimalkan kumpulan koneksi dan manajemen memori. Harap pertimbangkan untuk menggunakan kembali satu instance HttpClient. Setelah Anda melakukan ini, Anda dapat memasukkan handler palsu ke dalam satu instance HttpClient dan selesai.

Kedua - Anda benar. Desain API HttpClient tidak cocok untuk mekanisme pengujian berbasis antarmuka murni. Kami sedang mempertimbangkan untuk menambahkan metode statis/pola pabrik di masa mendatang yang memungkinkan Anda mengubah perilaku semua instans HttpClient yang dibuat dari pabrik itu atau setelah metode itu. Kami belum menetapkan desain untuk ini. Tetapi masalah utama akan tetap ada - Anda perlu mendefinisikan Handler palsu dan memasukkannya di bawah objek HttpClient.

@ericstj - pendapat tentang ini?

Terima kasih,
Sid.

@SidharthNabar mengapa Anda/tim begitu ragu untuk menawarkan antarmuka untuk kelas ini? Saya berharap bahwa inti dari pengembang harus _belajar_ tentang penangan dan kemudian harus _membuat_ kelas penangan palsu sudah cukup untuk membenarkan (atau paling tidak, menyoroti) perlunya antarmuka?

Ya, saya tidak mengerti mengapa Antarmuka menjadi hal yang buruk. Itu akan membuat pengujian HttpClient jauh lebih mudah.

Salah satu alasan utama kami menghindari antarmuka dalam kerangka kerja adalah karena versinya tidak baik. Orang-orang selalu dapat membuat sendiri jika mereka memiliki kebutuhan dan itu tidak akan rusak ketika versi berikutnya dari kerangka perlu menambahkan anggota baru. @KrzysztofCwalina atau @terrajobst mungkin dapat berbagi lebih banyak tentang sejarah/detail pedoman desain ini. Masalah khusus ini hampir menjadi perdebatan agama: Saya terlalu pragmatis untuk ambil bagian dalam hal itu.

HttpClient memiliki banyak opsi untuk unit testability. Itu tidak disegel dan sebagian besar anggotanya adalah virtual. Ini memiliki kelas dasar yang dapat digunakan untuk menyederhanakan abstraksi serta titik ekstensi yang dirancang dengan cermat di HttpMessageHandler seperti yang ditunjukkan @sharwell . IMO ini adalah API yang dirancang dengan cukup baik, terima kasih kepada @HenrikFrystykNielsen.

:+1: antarmuka

:wave: @ericstj Terima kasih banyak untuk melompat masuk :+1: :cake:

Salah satu alasan utama kami menghindari antarmuka dalam kerangka kerja adalah karena versinya tidak baik.

Ya - poin bagus.

Masalah khusus ini hampir menjadi perdebatan agama

ya ... titik baik diambil pada itu.

HttpClient memiliki banyak opsi untuk unit testability

Oh? Saya berjuang dalam hal ini :blush: maka alasan untuk masalah ini :blush:

Itu tidak disegel dan sebagian besar anggotanya adalah virtual.

Anggota virtual? Astaga, aku benar-benar merindukan itu! Jika mereka virtual, maka kerangka kerja mengejek dapat mengejek anggota tersebut :+1: dan kami tidak memerlukan antarmuka!

Mari kita lihat di HttpClient.cs ...

public Task<HttpResponseMessage> GetAsync(string requestUri) { .. }
public Task<HttpResponseMessage> GetAsync(Uri requestUri) { .. }

Hmm. itu bukan virtual ... mari kita cari file ... eh .. tidak ditemukan kata kunci virtual . Jadi, maksud Anda anggota _other_ untuk _kelas terkait_ lainnya adalah virtual? Jika demikian .. maka kita kembali ke masalah yang saya angkat - sekarang kita harus melihat di bawah tenda untuk melihat apa yang dilakukan GetAsync sehingga kita tahu apa yang harus dibuat/dihubungkan/dll...

Saya kira saya hanya tidak memahami sesuatu yang sangat mendasar, di sini (°_°)/¯ ?

EDIT: mungkin metode itu bisa virtual? Saya bisa PR!

SendAsync adalah virtual dan hampir semua lapisan API lainnya di atas itu. 'Sebagian besar' salah, ingatanku salah di sana. Kesan saya adalah bahwa sebagian besar secara efektif virtual karena mereka membangun di atas anggota virtual. Biasanya kami tidak membuat hal-hal virtual jika kelebihan beban mengalir. Ada kelebihan SendAsync yang lebih spesifik yang tidak virtual, yang bisa diperbaiki.

Ah! Gotcha :blush: Jadi semua metode itu akhirnya memanggil SendAsync .. yang melakukan semua pekerjaan berat. Jadi ini masih berarti kita memiliki masalah dapat ditemukan di sini .. tapi mari kita kesampingkan itu (itu berpendirian).

Bagaimana kita mengejek SendAsync memberikan contoh dasar ini...
Install-Package Microsoft.Net.Http
Install-Package xUnit

public class SomeService
{
    public async Task<string> GetSomeData(string url)
    {
        string result;
        using (var httpClient = new HttpClient())
        {
            result = await httpClient.GetStringAsync(url);
        }

        return result;
    }
}

public class Facts
{
    [Fact]
    public async Task GivenAValidUrl_GetSomeData_ReturnsSomeData()
    {
        // Arrange.
        var service = new SomeService();

        // Act.
        var result = await service.GetSomeData("http://www.google.com");

        // Assert.
        Assert.True(result.Length > 0);
    }
}

Salah satu alasan utama kami menghindari antarmuka dalam kerangka kerja adalah karena versinya tidak baik. Orang-orang selalu dapat membuat sendiri jika mereka memiliki kebutuhan dan itu tidak akan rusak ketika versi berikutnya dari kerangka perlu menambahkan anggota baru.

Saya benar-benar dapat memahami cara berpikir ini dengan .NET Framework yang lengkap.

Namun, .NET Core dibagi menjadi banyak paket kecil, masing-masing diversi secara independen. Gunakan versi semantik, benturkan versi utama ketika ada perubahan yang melanggar, dan Anda selesai. Satu-satunya orang yang terpengaruh akan menjadi orang-orang yang secara eksplisit memperbarui paket mereka ke versi utama yang baru - mengetahui ada perubahan yang melanggar yang didokumentasikan.

Saya tidak menganjurkan bahwa Anda harus memecah setiap antarmuka setiap hari: mengubah perubahan idealnya harus digabungkan menjadi versi utama yang baru.

Saya merasa sedih untuk melumpuhkan API karena alasan kompatibilitas di masa mendatang. Bukankah salah satu tujuan .NET Core untuk beralih lebih cepat karena Anda tidak perlu khawatir lagi tentang pembaruan .NET halus yang merusak ribuan aplikasi yang sudah diinstal?

dua sen saya.

@MrJul :+1:
Pembuatan versi seharusnya tidak menjadi masalah. Itu sebabnya nomor versi ada :)

Pembuatan versi masih menjadi masalah. Menambahkan anggota ke antarmuka adalah perubahan besar. Untuk pustaka inti seperti ini yang ada di kotak masuk di desktop, kami ingin mengembalikan fitur ke desktop di versi mendatang. Jika kita melakukan fork, artinya orang tidak dapat menulis kode portabel yang akan berjalan di kedua tempat. Untuk informasi lebih lanjut tentang melanggar perubahan, lihat: https://github.com/dotnet/corefx/wiki/Breaking-Changes.

Saya pikir ini adalah diskusi yang bagus, tetapi seperti yang saya sebutkan sebelumnya, saya tidak memiliki banyak goresan dalam argumen seputar antarmuka vs kelas abstrak. Mungkin ini adalah topik untuk dibawa ke pertemuan desain berikutnya?

Saya tidak terlalu familier dengan pustaka pengujian yang Anda gunakan, tetapi yang akan saya lakukan adalah menyediakan pengait untuk memungkinkan pengujian menyetel instance klien dan kemudian membuat objek tiruan yang berperilaku seperti yang saya inginkan. Objek tiruan bisa berubah untuk memungkinkan penggunaan kembali.

Jika Anda memiliki perubahan khusus yang kompatibel yang ingin Anda sarankan kepada HttpClient yang meningkatkan kemampuan pengujian unit, harap usulkan dan @SidharthNabar dan timnya dapat mempertimbangkannya.

Jika API tidak dapat diolok-olok, kami harus memperbaikinya. Tapi itu tidak ada hubungannya dengan antarmuka vs kelas. Antarmuka tidak berbeda dengan kelas abstrak murni dari perspektif mockability, untuk semua tujuan praktis.

@KrzysztofCwalina Anda, Pak, kena paku di kepala! ringkasan yang sempurna!

Saya kira saya akan mengambil sisi yang kurang populer dari argumen ini karena menurut saya pribadi tidak diperlukan antarmuka. Seperti yang telah ditunjukkan, HttpClient sudah merupakan abstraksi. Perilaku tersebut benar-benar berasal dari HttpMessageHandler . Ya, itu tidak dapat diolok-olok/dipalsukan menggunakan pendekatan yang telah kita biasakan dengan kerangka kerja seperti Moq atau FakeItEasy, tetapi itu tidak perlu; itu bukan _only_ cara melakukan sesuatu. API masih dapat diuji dengan sempurna berdasarkan desain.

Jadi mari kita bahas "Saya perlu membuat HttpMessageHandler saya sendiri?". Tidak, tentu saja tidak. Kami tidak semua menulis perpustakaan mengejek kami sendiri. @PureKrome telah menunjukkan perpustakaan HttpClient.Helpers -nya. Secara pribadi saya belum pernah menggunakan yang itu, tetapi saya akan memeriksanya. Saya telah menggunakan perpustakaan MockHttp @richardszalay yang menurut saya fantastis. Jika Anda pernah bekerja dengan $httpBackend AngularJS, MockHttp mengambil pendekatan yang persis sama.

Dari segi ketergantungan, kelas layanan Anda harus mengizinkan HttpClient untuk disuntikkan dan jelas dapat menyediakan default yang wajar new HttpClient() . Jika Anda perlu dapat membuat instance, ambil Func<HttpClient> atau, jika Anda bukan penggemar Func<> karena alasan apa pun, rancang abstraksi IHttpClientFactory Anda sendiri dan implementasi default dari itu, sekali lagi, hanya mengembalikan new HttpClient() .

Sebagai penutup, saya menemukan API ini dapat diuji dengan sempurna dalam bentuknya saat ini dan tidak melihat perlunya antarmuka. Ya, itu memerlukan pendekatan yang berbeda, tetapi pustaka yang disebutkan di atas sudah ada untuk membantu kami dengan gaya pengujian yang berbeda ini dengan cara yang sangat logis dan intuitif. Jika kita menginginkan lebih banyak fungsionalitas/kesederhanaan, mari berkontribusi pada perpustakaan tersebut untuk membuatnya lebih baik.

Secara pribadi, sebuah Antarmuka atau metode virtual tidak terlalu penting bagi saya. Tapi ayolah, perfectly testable sedikit berlebihan :)

@luisrudge Nah, bisakah Anda memberikan skenario yang tidak dapat diuji menggunakan gaya pengujian penangan pesan yang diaktifkan oleh sesuatu seperti MockHttp? Mungkin itu akan membantu membuat kasus ini.

Saya belum menemukan apa pun yang belum dapat saya kodifikasi, tetapi mungkin saya melewatkan beberapa skenario esoteris yang tidak dapat dibahas. Meski begitu, mungkin hanya fitur perpustakaan yang hilang yang dapat disumbangkan seseorang.

Untuk saat ini saya mempertahankan pendapat bahwa itu "dapat diuji dengan sempurna" sebagai ketergantungan, hanya saja tidak dengan cara yang biasa dilakukan oleh .NET devs.

Ini dapat diuji, saya setuju. tapi tetap saja sakit. Jika Anda harus menggunakan lib eksternal yang membantu Anda melakukannya, itu tidak dapat diuji dengan sempurna. Tapi saya rasa itu terlalu subjektif untuk dibahas.

Orang dapat berargumen bahwa aspnet mvc 5 dapat diuji dengan sempurna, Anda hanya perlu menulis 50LOC untuk mengejek setiap hal yang dibutuhkan pengontrol. IMHO, itu kasus yang sama dengan HttpClient. Ini bisa diuji? Ya. Sangat mudah untuk menguji? Tidak.

@luisrudge Ya, saya setuju, ini subjektif dan saya sepenuhnya memahami keinginan untuk antarmuka/virtual. Saya hanya mencoba untuk memastikan siapa pun yang datang dan membaca utas ini setidaknya akan mendapatkan pendidikan tentang fakta bahwa API ini _dapat_ dimanfaatkan dalam basis kode dengan cara yang sangat dapat diuji tanpa memperkenalkan semua abstraksi Anda sendiri di sekitar HttpClient untuk sampai ke sana.

jika Anda harus menggunakan lib eksternal yang membantu Anda melakukannya, itu tidak dapat diuji dengan sempurna

Yah, kita semua menggunakan satu perpustakaan atau yang lain untuk mengejek/memalsukan sudah * dan, memang benar, kita tidak bisa hanya menggunakan _that_ satu untuk menguji gaya API ini, tapi saya tidak berpikir itu berarti kurang dapat diuji daripada pendekatan berbasis antarmuka hanya karena saya harus membawa perpustakaan lain.

_ * Setidaknya saya harap tidak!_

@ drub0y dari OP saya, saya telah menyatakan bahwa perpustakaan dapat diuji - tetapi itu benar-benar menyakitkan dibandingkan (dengan apa yang saya yakini dengan penuh semangat) dengan apa yang _bisa_ menjadi. IMO @luisrudge mengejanya dengan sempurna:

Orang dapat berargumen bahwa aspnet mvc 5 dapat diuji dengan sempurna, Anda hanya perlu menulis 50LOC untuk mengejek setiap hal yang dibutuhkan pengontrol

Repo ini adalah bagian utama dari sejumlah _besar_ pengembang. Jadi taktik default (dan dapat dimengerti) adalah _sangat_ berhati-hati dengan repo ini. Tentu - saya benar-benar mengerti.

Saya ingin percaya bahwa repo ini masih dalam posisi untuk diubah (sehubungan dengan API) sebelum menjadi RTW. Perubahan di masa depan tiba-tiba menjadi _sangat_ sulit dilakukan, termasuk _persepsi_ untuk diubah.

Jadi dengan api saat ini - dapatkah itu diuji? Ya. Apakah itu lulus tes Pengembang Materi Gelap? Saya pribadi tidak berpikir begitu.

Tes lakmus IMO adalah ini: dapatkah joe mengambil salah satu kerangka kerja pengujian umum/populer + kerangka kerja mengejek dan mengejek salah satu metode utama dalam API ini? Sekarang - tidak. Jadi, pengembang perlu menghentikan apa yang mereka lakukan dan mulai mempelajari _implimentasi_ perpustakaan ini.

Seperti yang telah ditunjukkan, HttpClient sudah merupakan abstraksi. Perilaku tersebut benar-benar berasal dari HttpMessageHandler.

Ini adalah poin saya - mengapa Anda membuat kami harus menghabiskan siklus mencari tahu ini ketika tujuan perpustakaan adalah untuk mengabstraksi semua keajaiban itu .. hanya untuk mengatakan "Ok .. jadi kami telah melakukan beberapa keajaiban tetapi sekarang Anda ingin mengejek sihir kami ... Maaf, Anda sekarang harus menarik lengan baju Anda, mengangkat atap perpustakaan dan mulai menggali di dalam, dll".

Rasanya begitu... berbelit-belit.

Kami berada dalam posisi untuk membuat hidup lebih mudah bagi banyak orang dan terus kembali ke posisi bertahan: "Itu bisa dilakukan - tapi .. baca FAQ/Kode".

Berikut sudut pandang lain untuk mendekati masalah ini: Berikan 2 contoh kepada pengembang Non-MS acak ... joe pengembang warga yang tahu cara menggunakan xUnit dan Moq/FakeItEasy/InsertTheHipMockingFrameworkThisYear.

  • API pertama yang menggunakan antarmuka/virtual/apa pun .. tetapi metodenya dapat diejek.
  • API kedua yang hanya merupakan metode publik dan untuk mengejek _titik integrasi_ .. mereka harus membaca kodenya.

Itu menyaring ke itu, IMO.

Lihat pengembang mana yang dapat memecahkan masalah itu _dan_ tetap senang.

Jika API tidak dapat diolok-olok, kami harus memperbaikinya.

Saat ini bukan IMO - tetapi ada cara untuk mengatasi ini dengan sukses (sekali lagi, itu pendapat - saya akui itu)

Tapi itu tidak ada hubungannya dengan antarmuka vs kelas. Antarmuka tidak berbeda dengan kelas abstrak murni dari perspektif mockability, untuk semua tujuan praktis.

Tepat - itu adalah detail implementasi. Tujuannya adalah untuk dapat menggunakan kerangka kerja tiruan yang telah teruji dalam pertempuran dan pergilah.

@luisrudge jika Anda harus menggunakan lib eksternal yang membantu Anda melakukannya, itu tidak dapat diuji dengan sempurna
@drub0y Yah, kita semua sudah menggunakan satu perpustakaan atau yang lain untuk mengejek/memalsukan

(Saya harap saya mengerti kutipan/paragraf terakhir itu..) Tidak ... tenang. Apa yang @luisrudge katakan adalah: "Kami memiliki satu alat untuk pengujian. Alat kedua untuk mengejek. Sejauh ini, alat generik/keseluruhan itu tidak terikat pada apa pun. Tapi .. Anda sekarang ingin saya mengunduh alat ke-3 yang _spesifik_ untuk mengejek API/layanan _spesifik_ dalam kode kami karena API/layanan tertentu yang kami gunakan, tidak dirancang untuk diuji dengan baik?". Itu sedikit kaya :(

Dari segi ketergantungan, kelas layanan Anda harus mengizinkan HttpClient untuk disuntikkan dan jelas dapat menyediakan default yang wajar dari new HttpClient()

Senyawa! Jadi, dapatkah Anda memfaktorkan ulang kode contoh di atas untuk menunjukkan kepada kami betapa mudahnya hal ini? Ada banyak cara untuk menguliti kucing... jadi apa cara yang sederhana DAN mudah? Tunjukkan pada kami kodenya.

Saya akan mulai. Permintaan maaf kepada @KrzysztofCwalina karena menggunakan antarmuka, tetapi ini hanya untuk memulai tes lakmus saya.

public interface IHttpClient
{
    Task<string> GetStringAsync(string url);
}

public class SomeService
{
    private IHttpClient _httpClient;

    // Injected.
    public SomeService(IHttpClient httpClient) { .. }

    public async Task<string> GetSomeData(string url)
    {
        string result;
        using (var httpClient = _httpClient ?? new HttpClient())
        {
            result = await httpClient.GetStringAsync(url);
        }

        return result;
    }    
}

Kedengarannya seperti yang dibutuhkan @PureKrome adalah pembaruan dokumentasi yang menjelaskan metode mana yang harus diejek/ditimpa untuk menyesuaikan perilaku API selama pengujian.

@sharwell itu sama sekali tidak terjadi. Ketika saya menguji hal-hal, saya tidak ingin menjalankan seluruh kode httpclient:
Lihat metode SendAsync .
Ini gila. Apa yang Anda sarankan adalah bahwa setiap pengembang harus mengetahui internal dari kelas, buka github, lihat kode dan hal-hal semacam itu untuk dapat mengujinya. Saya ingin pengembang itu mengejek GetStringAsync dan menyelesaikannya.

@luisrudge Sebenarnya saran saya adalah menghindari ejekan untuk perpustakaan ini sama sekali. Karena Anda sudah memiliki lapisan abstraksi yang bersih dengan implementasi yang dapat diganti dan dipisahkan, ejekan tambahan tidak diperlukan. Mengejek untuk perpustakaan ini memiliki kerugian tambahan berikut:

  1. Peningkatan beban pada pengembang yang membuat tes (memelihara tiruan), yang pada gilirannya meningkatkan biaya pengembangan
  2. Peningkatan kemungkinan bahwa pengujian Anda akan gagal mendeteksi perilaku bermasalah tertentu, seperti menggunakan kembali aliran konten yang terkait dengan pesan (mengirim pesan menghasilkan panggilan ke Dispose pada aliran konten, tetapi sebagian besar ejekan akan mengabaikan ini)
  3. Penggabungan aplikasi yang tidak perlu lebih ketat ke lapisan abstraksi tertentu, yang meningkatkan biaya pengembangan yang terkait dengan evaluasi alternatif untuk HttpClient

Mengejek adalah strategi pengujian yang menargetkan basis kode yang saling terkait yang sulit untuk diuji dalam skala kecil. Sementara penggunaan ejekan berkorelasi dengan peningkatan biaya pengembangan, _kebutuhan_ untuk ejekan berkorelasi dengan peningkatan jumlah kopling dalam kode. Ini berarti mengejek itu sendiri adalah bau kode. Jika Anda dapat memberikan cakupan pengujian input-output yang sama di seluruh dan di dalam API Anda tanpa menggunakan ejekan, pada dasarnya Anda akan mendapat manfaat di setiap aspek pengembangan.

Karena Anda sudah memiliki lapisan abstraksi yang bersih dengan implementasi yang dapat diganti dan dipisahkan, ejekan tambahan tidak diperlukan

Dengan hormat, jika opsinya adalah harus menulis Penangan Pesan Palsu Anda sendiri, yang tidak jelas tanpa menggali kodenya, maka itu adalah penghalang yang sangat tinggi yang akan sangat membuat frustasi bagi banyak pengembang.

Saya setuju dengan komentar @luisrudge sebelumnya. _Secara teknis_ MVC5 dapat diuji, tetapi Tuhan apakah itu menyebalkan dan sumber yang sangat besar untuk menarik rambut.

@sharwell Apakah Anda mengatakan bahwa mengejek IHttpClient adalah beban dan menerapkan penangan pesan palsu (seperti yang ini tidak? Saya tidak setuju dengan itu, maaf.

@luisrudge Untuk kasus sederhana pengujian GetStringAsync , hampir tidak sulit untuk mengejek pawang. Menggunakan Moq + HttpClient out-of-the-box, yang Anda butuhkan hanyalah ini:

Uri requestUri = new Uri("http://google.com");
string expectedResponse = "Response text";

Mock<HttpClientHandler> mockHandler = new Mock<HttpClientHandler>();
mockHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(message => message.RequestUri == requestUri), ItExpr.IsAny<CancellationToken>())
    .Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedResponse) }));
HttpClient httpClient = new HttpClient(mockHandler.Object);
string result = await httpClient.GetStringAsync(requestUri).ConfigureAwait(false);
Assert.AreEqual(expectedResponse, result);

Jika itu terlalu banyak, Anda dapat mendefinisikan kelas pembantu:

internal static class MockHttpClientHandlerExtensions
{
    public static void SetupGetStringAsync(this Mock<HttpClientHandler> mockHandler, Uri requestUri, string response)
    {
        mockHandler.Protected()
            .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(message => message.RequestUri == requestUri), ItExpr.IsAny<CancellationToken>())
            .Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(response) }));
    }
}

Menggunakan kelas pembantu yang Anda butuhkan hanyalah ini:

Uri requestUri = new Uri("http://google.com");
string expectedResponse = "Response text";

Mock<HttpClientHandler> mockHandler = new Mock<HttpClientHandler>();
mockHandler.SetupGetStringAsync(requestUri, expectedResponse);
HttpClient httpClient = new HttpClient(mockHandler.Object);
string result = await httpClient.GetStringAsync(requestUri).ConfigureAwait(false);
Assert.AreEqual(expectedResponse, result);

Jadi untuk membandingkan dua versi:
_Disclaimer_: ini belum diuji, kode browser. Juga, saya sudah lama tidak menggunakan Moq.

Saat ini

public class SomeService(HttpClientHandler httpClientHandler= null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _handler == null
                                  ? new HttpClient
                                  : new HttpClient(handler))
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

// Unit test...
// Arrange.
Uri requestUri = new Uri("http://google.com");
string expectedResponse = "Response text";
var responseMessage = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedResponse) };
Mock<HttpClientHandler> mockHandler = new Mock<HttpClientHandler>();
mockHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(message => message.RequestUri == requestUri), ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(responseMessage);
HttpClient httpClient = new HttpClient(mockHandler.Object);
var someService = new SomeService(mockHandler.Object); // Injected...

// Act.
string result = await someService.GetSomethingFromTheInternetAsync();

// Assert.
Assert.AreEqual(expectedResponse, result);
httpClient.Verify(x => x.GetSomethingFromTheInternetAsync(), Times.Once);

versus menawarkan cara untuk mengejek metode API secara langsung.

Cara lain

public class SomeService(IHttpClient httpClient = null)
{
    public async Foo GetSomethingFromTheInternetAsync()
    {
        ....
        using (var httpClient = _httpClient ?? new HttpClient())
        {
            html = await httpClient.GetStringAsync("http://www.google.com.au");
        }
        ....
    } 
}

// Unit tests...
// Arrange.
var response = new Foo();
var httpClient = new Mock<IHttpClient>();
httpClient.Setup(x => x.GetStringAsync(It.IsAny<string>))
    .ReturnsAsync(response);
var service = new SomeService(httpClient.Object); // Injected.

// Act.
var result = await service.GetSomethingFromTheInternetAsync();

// Assert.
Assert.Equals("something", foo.Something);
httpClient.Verify(x => x.GetStringAsync(It.IsAny<string>()), Times.Once);

menyaringnya, itu terutama sampai pada ini (tidak termasuk sedikit perbedaan dalam SomeService ) ...

var responseMessage = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedResponse) };
Mock<HttpClientHandler> mockHandler = new Mock<HttpClientHandler>();
mockHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(message => message.RequestUri == requestUri), ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(responseMessage);
HttpClient httpClient = new HttpClient(mockHandler.Object);

vs ini...

var httpClient = new Mock<IHttpClient>();
httpClient.Setup(x => x.GetStringAsync(It.IsAny<string>))
    .ReturnsAsync(response);

Apakah kode _roughly_ terlihat tepat untuk keduanya? (keduanya harus cukup akurat, sebelum kita dapat membandingkan keduanya).

@sharwell jadi saya harus menjalankan kode saya melalui semua kode HttpClient.cs untuk menjalankan tes saya? Bagaimana itu kurang digabungkan dengan HttpClient?

Kami banyak menggunakan HttpClient di beberapa sistem kami dan saya tidak akan menyebutnya sebagai bagian dari teknologi yang dapat diuji dengan baik. Saya sangat suka perpustakaan tetapi memiliki semacam Antarmuka atau peningkatan lain untuk pengujian akan menjadi peningkatan besar IMO.

Kudo's to @PureKrome untuk perpustakaannya, tetapi akan lebih baik jika itu tidak perlu :)

Saya baru saja membaca semua komentar dan belajar banyak, tetapi saya punya satu pertanyaan: mengapa Anda perlu mengejek HttpClient dalam pengujian Anda?

Dalam skenario paling umum, fungsionalitas yang disediakan oleh HttpClient sudah akan dibungkus dalam beberapa kelas seperti HtmlDownloader.DownloadFile() . Mengapa tidak mengejek kelas ini saja, bukan pipa ledengnya?

Skenario lain yang mungkin memerlukan ejekan seperti itu adalah ketika Anda harus mengurai string yang diunduh dan potongan kode ini memerlukan pengujian untuk beberapa keluaran berbeda. Bahkan dalam kasus ini, fungsi ini (penguraian) dapat diekstraksi ke kelas/metode dan diuji tanpa tiruan.

Saya tidak mengatakan bahwa testabilitasnya tidak dapat ditingkatkan, juga tidak menyiratkan bahwa itu tidak boleh. Saya hanya ingin mengerti untuk mempelajari beberapa hal baru.

@tucaz Mari kita bayangkan saya memiliki situs web yang mengembalikan beberapa informasi tentang diri Anda. Usia Anda, nama, tanggal lahir, dll. Anda juga memiliki saham di beberapa perusahaan.

Anda pernah mengatakan bahwa Anda memiliki 100 unit saham AAA dan 200 unit saham BBB? Jadi, saat kami menampilkan detail akun Anda, kami _harus_ menampilkan berapa nilai saham Anda.

Untuk melakukan ini, kita perlu mengambil harga saham saat ini dari sistem _another_. Jadi untuk melakukan ini, kita perlu mengambil stok dengan layanan, bukan? Mari untuk ini...

_Penafian: kode browser..._

public class StockService : IStockService
{
    private readonly IHttpClient _httpClient;

    public StockService(IHttpClient httpClient)
    {
        _httpClient = httpClient; // Optional.
    }

    public async Task<IEnumerable<StockResults>> GetStocksAsync(IEnumerable<string> stockCodes)
    {
        var queryString = ConvertStockCodeListIntoQuerystring();
        string jsonResult;
        using (var httpClient = _httpClient ?? new HttpClient())
        {
            jsonResult = await httpClient.GetStringAsync("http:\\www.stocksOnline.com\stockResults?" + queryString);
        }

        return JsonConvert.DeserializeObject<StockResult>(jsonResult);
    }
}

Ok, jadi di sini kami memiliki layanan sederhana yang mengatakan: "_Go get me the stock prices for stock AAA and BBB_";

Jadi, saya perlu menguji ini:

  • Diberikan beberapa nama saham yang sah
  • Diberikan beberapa nama saham yang tidak valid/tidak ada
  • httpClient melempar pengecualian (internet meledak sambil menunggu tanggapan)

dan sekarang saya dapat dengan mudah menguji skenario itu. Saya perhatikan bahwa ketika httpClient meledak (melempar pengecualian) .. layanan ini meledak .. jadi mungkin saya perlu menambahkan beberapa logging dan mengembalikan nol? donno.. tapi setidaknya saya bisa memikirkan masalahnya dan kemudian menanganinya.

Tentu saja - saya 100% tidak ingin benar-benar mencapai layanan web nyata selama pengujian unit normal saya. Dengan menyuntikkan tiruan, saya bisa menggunakannya. Kalau tidak, saya dapat membuat instance baru dan menggunakannya.

Jadi saya mencoba untuk menetapkan bahwa saran saya jauh lebih mudah (untuk membaca/mempertahankan/dll) daripada status quo saat ini.

Dalam kebanyakan skenario umum, fungsionalitas yang disediakan oleh HttpClient sudah akan dibungkus dalam beberapa kelas seperti HtmlDownloader.DownloadFile().

Mengapa kita harus membungkus HttpClient? Ini _sudah_ merupakan abstraksi dari semua http :sparkles: di bawahnya. Ini perpustakaan yang bagus! Saya pribadi tidak melihat apa yang akan dicapai?

@PureKrome contoh penggunaan Anda persis seperti yang saya maksud dengan membungkus fungsionalitas dalam kelas pembungkus.

Saya tidak yakin saya dapat memahami apa yang Anda coba uji, tetapi bagi saya sepertinya Anda sedang menguji layanan web yang mendasarinya ketika yang benar-benar perlu Anda tegaskan adalah bahwa siapa pun yang mengonsumsinya akan dapat menangani apa pun yang kembali dari GetStockAsync metode.

Harap pertimbangkan hal berikut:

private IStockService stockService;

[Setup]
public void Setup()
{
    stockService = A.Fake<IStockService>();

    A.CallTo(() => stockService.GetStocksAsync(new [] { "Ticker1" }))
        .Returns(new StockResults[] { new StockResults { Price = 100m, Ticker = "Ticker1", Status = "OK" }});
    A.CallTo(() => stockService.GetStocksAsync(new [] { "Ticker2" }))
        .Returns(new StockResults[] { new StockResults { Price = 0m, Ticker = "Ticker2", Status = "NotFound" }});
    A.CallTo(() => stockService.GetStocksAsync(new [] { "Ticker3" }))
        .Throws(() => new InvalidOperationException("Some weird message"));
}

[Test]
public async void Get_Total_Stock_Quotes()
{
    var stockService = A.Fake<IStockService>();

    var total = await stockService.GetStockAsync(new [] { "Ticker1" });

    Assert.IsNotNull(total);
    Assert.IsGreaterThan(0, total.Sum(x => x.Price);
}

[Test]
public async void Hints_When_Ticker_Not_Found()
{
    var stockService = A.Fake<IStockService>();

    var total = await stockService.GetStockAsync(new [] { "Ticker2" });

    Assert.IsNotNull(total);
    Assert.AnyIs(x => x.Status == "NotFound");
}

[Test]
public async void Throws_InvalidOperationException_When_Error()
{
    var stockService = A.Fake<IStockService>();

    Assert.Throws(() => await stockService.GetStockAsync(new [] { "Ticker3" }), typeof(InvalidOperationException));
}

Saya mengusulkan agar Anda meningkatkan abstraksi Anda satu tingkat dan menangani logika bisnis yang sebenarnya karena tidak ada apa pun yang perlu diuji mengenai perilaku metode GetStockAsync .

Satu-satunya skenario yang saya pikir perlu untuk mengejek HttpClient adalah ketika Anda perlu menguji beberapa logika coba lagi tergantung pada respons yang diterima dari server atau sesuatu seperti itu. Dalam hal ini, Anda akan membangun bagian kode yang lebih rumit yang mungkin akan diekstraksi ke dalam kelas HttpClientWithRetryLogic dan tidak menyebar di setiap metode yang Anda kodekan yang menggunakan kelas HttpClient .

Jika demikian, Anda dapat menggunakan pengendali pesan seperti yang disarankan atau membangun kelas untuk membungkus semua logika coba lagi ini karena HttpClient akan menjadi bagian kecil dari hal penting yang dilakukan kelas Anda.

@tucaz Bukankah Anda baru saja menguji bahwa palsu Anda mengembalikan apa yang Anda konfigurasikan, secara efektif menguji perpustakaan mengejek? @PureKrome ingin menguji implementasi konkret StockService .

Level abstraksi yang baru diperkenalkan harus membungkus panggilan HttpClient agar dapat menguji StockService dengan benar. Kemungkinannya adalah bahwa pembungkus hanya akan mendelegasikan setiap panggilan ke HttpClient , dan menambahkan kerumitan yang tidak perlu ke aplikasi.

Anda selalu dapat menambahkan lapisan abstraksi baru untuk menyembunyikan jenis yang tidak dapat diolok-olok. Apa yang dikritik di utas ini adalah bahwa mengejek HttpClient dimungkinkan, tetapi mungkin lebih mudah / sejalan dengan apa yang biasanya dilakukan dengan perpustakaan lain.

@MrJul Anda benar. Itu sebabnya saya mengatakan bahwa apa yang telah diuji oleh contohnya adalah implementasi layanan web itu sendiri dan mengapa saya mengusulkan (tanpa contoh kode. Saya buruk) bahwa dia mengolok-olok IStockService sehingga dia dapat menyatakan bahwa kode yang memakannya dapat menangani apa pun yang dikembalikan GetStockAsync .

Itu yang menurut saya harus diuji terlebih dahulu. Bukan kerangka kerja mengejek (seperti yang saya lakukan) atau implementasi layanan web (seperti yang ingin dilakukan @PureKrome ).

Pada akhirnya saya dapat memahami bahwa akan menyenangkan memiliki antarmuka IHttpClient , tetapi gagal melihat utilitas apa pun dalam skenario semacam ini karena IMO yang perlu diuji adalah bagaimana konsumen abstraksi layanan (dan karenanya layanan web) dapat menangani pengembaliannya dengan tepat.

@tucaz tidak. Tes Anda salah. Anda sedang menguji perpustakaan yang mengejek. Menggunakan contoh @PureKrome , seseorang ingin menguji tiga hal:

  • jika permintaan http menggunakan METHOD yang benar
  • jika permintaan http memiliki hak URL
  • jika badan respons dapat berhasil dideserialisasi dengan deserializer json

Jika Anda hanya mengejek IStockService di Controller Anda (misalnya), Anda tidak menguji satu pun dari tiga hal yang disebutkan di atas.

@luisrudge setuju dengan semua poin Anda. Tapi... apakah nilai dari menguji semua hal yang Anda sebutkan ini sepadan? Maksud saya, jika saya berkomitmen dalam menguji semua hal dasar yang dicakup oleh banyak kerangka kerja (json.net, httpclient, dll) saya pikir saya dapat menangani cara yang mungkin dilakukan hari ini.

Namun, jika saya ingin memastikan saya dapat menghapus beberapa respons aneh, tidak bisakah saya membuat tes dengan mengisolasi aspek-aspek kode tersebut tanpa perlu menambahkan HttpClient di dalam tes? Juga, tidak ada cara untuk mencegah bahwa server akan mengirimkan respons yang benar yang Anda harapkan dalam pengujian Anda sepanjang waktu. Bagaimana jika ada respon yang tidak tercakup? Tes Anda tidak akan mendapatkannya dan Anda akan tetap memiliki masalah.

Pendapat saya adalah bahwa tes semacam itu yang Anda usulkan memiliki nilai bersih minimal dan Anda harus mengerjakannya hanya jika Anda memiliki 99% aplikasi lainnya yang tercakup. Tetapi pada akhirnya itu semua adalah masalah preferensi pribadi dan ini adalah subjek subjektif di mana orang yang berbeda melihat nilai yang berbeda. Dan ini, tentu saja, tidak berarti bahwa HttpClient seharusnya tidak lebih dapat diuji.

Saya pikir desain potongan kode khusus ini memperhitungkan semua poin ini dan bahkan jika itu bukan hasil yang optimal, saya tidak berpikir bahwa biaya untuk mengubahnya sekarang akan lebih rendah daripada manfaat yang mungkin didapat.

Tujuan yang ada dalam pikiran saya ketika saya pertama kali berbicara adalah untuk membantu @PureKrome mencoba untuk tidak memfokuskan usahanya pada apa yang saya anggap sebagai tugas bernilai rendah. Tetapi sekali lagi, semuanya bermuara pada pendapat pribadi di beberapa titik.

Yakin Anda menekan url yang benar memiliki nilai bersih yang rendah untuk Anda? Bagaimana dengan memastikan Anda melakukan permintaan PUT untuk mengedit sumber daya alih-alih posting? 99% aplikasi saya lainnya dapat dicakup, jika panggilan ini gagal, maka tidak ada yang akan berhasil.

Soooo, saya benar-benar berpikir berbicara tentang HttpClient::GetStringAsync adalah cara yang buruk untuk membuat kasus karena dapat mengejek HttpClient di tempat pertama. Jika Anda menggunakan salah satu metode HttpClient::Get[String|Stream|ByteArray]Async yang telah hilang karena Anda tidak dapat melakukan penanganan kesalahan yang tepat karena semuanya tersembunyi di balik HttpRequestException yang akan dilempar dan itu tidak 'bahkan tidak mengekspos detail penting seperti kode status HTTP. Jadi itu berarti abstraksi apa pun yang Anda buat di atas HttpClient tidak akan memiliki kemampuan untuk bereaksi dan menerjemahkan kesalahan HTTP tertentu ke pengecualian khusus domain dan sekarang Anda memiliki abstraksi yang bocor... yang memberikan informasi pengecualian yang mengerikan kepada penelepon layanan domain Anda.

Saya pikir penting untuk menunjukkan ini karena, dalam banyak kasus, ini berarti jika Anda menggunakan pendekatan mengejek, Anda akan mengejek metode HttpClient yang mengembalikan HttpResponseMessage yang sudah membawa Anda ke jumlah pekerjaan yang sama yang diperlukan untuk mengejek/memalsukan HttpMessageHandler::SendAsync . Bahkan, itu lebih! Anda berpotensi harus mengejek beberapa metode (misalnya [Get|Put|Delete|Send]Async ) pada level HttpClient daripada hanya mengejek SendAsync pada level HttpMessageHandler .

@luisrudge juga menyebutkan deserializing JSON, jadi sekarang kita berbicara tentang bekerja dengan HttpContent yang berarti Anda bekerja dengan salah satu metode yang mengembalikan HttpResponseMessage pasti. Kecuali jika Anda mengatakan Anda melewati seluruh arsitektur formatter media dan menghapus serialisasi string dari GetStringAsync menjadi instance objek secara manual dengan JsonSerializer::Deserialize atau apa pun. Jika itu masalahnya, saya rasa kita tidak dapat benar-benar berbicara tentang _testing_ API lagi karena itu hanya akan menggunakan API _itself_ sepenuhnya salah. :kecewa:

@luisrudge memang penting, tetapi Anda tidak perlu menyentuh HttpClient untuk memastikan itu benar. Pastikan saja bahwa sumber tempat Anda membaca URL mengembalikan hal-hal dengan benar dan Anda siap melakukannya. Sekali lagi, tidak perlu melakukan tes integrasi dengan HttpClient di dalamnya.

Ini adalah hal-hal penting, tetapi begitu Anda melakukannya dengan benar, kemungkinan mengacaukannya sangat, sangat rendah kecuali Anda memiliki pipa bocor di tempat lain.

@tucaz setuju. Tetap: Ini adalah bagian terpenting dan saya ingin mengujinya :)

@drub0y Saya kagum bahwa menggunakan metode publik salah :)

@luisrudge Yah, diskusi yang berbeda saya kira, tapi saya tidak merasa itu membuat kasus untuk antarmuka yang dapat diejek dengan mengabaikan kekurangan yang melekat dalam penggunaan metode "kenyamanan" ini di semua kecuali skenario pengkodean yang paling perbaikan (misalnya aplikasi utilitas dan demo panggung). Jika Anda memilih untuk menggunakannya dalam kode produksi, itu pilihan Anda, tetapi Anda melakukannya sambil berharap mengakui kekurangannya.

Bagaimanapun, mari kita kembali ke apa yang ingin dilihat oleh @PureKrome yaitu bagaimana seseorang harus mendesain kelas mereka agar sesuai dengan desain API HttpClient sehingga mereka dapat dengan mudah menguji kode mereka.

Pertama, berikut adalah contoh layanan domain yang akan melakukan panggilan HTTP untuk memenuhi kebutuhan domainnya:

``` c#
antarmuka publik ISomeDomainService
{
TugasGetSomeThingsValueAsync(string id);
}

kelas publik SomeDomainService : ISomeDomainService
{
pribadi hanya dapat dibaca HttpClient _httpClient;

public SomeDomainService() : this(new HttpClient())
{

}

public SomeDomainService(HttpClient httpClient)
{
    _httpClient = httpClient;
}

public async Task<string> GetSomeThingsValueAsync(string id)
{
    return await _httpClient.GetStringAsync("/things/" + Uri.EscapeUriString(id));
}

}

As you can see, it allows an `HttpClient` instance to be injected and also has a default constructor that instantiates the default `HttpClient` with no other special settings (obviously this default instance can be customized, but I'll leave that out for brevity).

Ok, so what's a test method look like for this thing using the MockHttp library then? Let's see:

``` c#
[Fact]
public async Task GettingSomeThingsValueReturnsExpectedValue()
{
    // Arrange
    var mockHttpMessageHandler = new MockHttpMessageHandler();
    mockHttpMessageHandler.Expect("http://unittest/things/123")
        .Respond(new StringContent("expected value"));

    SomeDomainService sut = new SomeDomainService(new HttpClient(mockHttpMessageHandler)
    {
        BaseAddress = new Uri("http://unittest")
    });

    // Act
    var value = await sut.GetSomeThingsValueAsync("123");

    // Assert
    value.Should().Be("expected value");
}

Err, itu cukup sederhana dan lurus ke depan; membaca jelas sebagai hari IMNSHO. Juga, perhatikan bahwa saya memanfaatkan FluentAssertions yang, sekali lagi, adalah perpustakaan lain yang saya pikir banyak dari kita gunakan dan serangan lain terhadap argumen "Saya tidak ingin harus membawa perpustakaan lain".

Sekarang, izinkan saya juga mengilustrasikan mengapa Anda mungkin tidak pernah _benar-benar_ ingin menggunakan GetStringAsync untuk kode produksi apa pun. Lihat kembali metode SomeDomainService::GetSomeThingsValueAsync dan anggap itu memanggil REST API yang benar-benar mengembalikan kode status HTTP yang berarti. Misalnya, mungkin "benda" dengan id "123" tidak ada sehingga API akan mengembalikan status 404. Jadi saya ingin mendeteksi itu dan memodelkannya sebagai pengecualian khusus domain berikut:

``` c#
// dukungan serialisasi dikecualikan untuk singkatnya
kelas publik SomeThingDoesntExistException : Pengecualian
{
SomeThingDoesntExistException publik (string id)
{
identitas = identitas;
}

public string Id
{
    get;
    private set;
}

}

Ok, so let's rewrite the `SomeDomainService::GetSomeThingsValueAsync` method to do that now:

``` c#
public async Task<string> GetSomeThingsValueAsync(string id)
{
    try
    {
        return await _httpClient.GetStringAsync("/things/" + Uri.EscapeUriString(id));
    }
    catch(HttpRequestException requestException)
    {
        // uh, oh... no way to tell if it doesn't exist (404) or server maybe just errored (500)
    }
}

Jadi itulah yang saya bicarakan ketika saya mengatakan bahwa metode "kenyamanan" dasar seperti GetStringAsync tidak benar-benar "baik" untuk kode tingkat produksi. Yang bisa Anda dapatkan hanyalah HttpRequestException dan dari sana Anda tidak dapat memberi tahu kode status apa yang Anda dapatkan dan karena itu tidak mungkin menerjemahkannya ke dalam pengecualian yang benar dalam sebuah domain. Sebagai gantinya, Anda harus menggunakan GetAsync dan menafsirkan HttpResponseMessage seperti:

``` c#
Tugas asinkron publikGetSomeThingsValueAsync(string id)
{
HttpResponseMessage responseMessage = menunggu _httpClient.GetAsync("/things/" + Uri.EscapeUriString(id));

if(responseMessage.IsSuccessStatusCode)
{
    return await responseMessage.Content.ReadAsStringAsync();
}
else
{
    switch(responseMessage.StatusCode)
    {
        case HttpStatusCode.NotFound:
            throw new SomeThingDoesntExistException(id);

        // any other cases you want to might want to handle specifically for your domain

        default:
            // Unhandled cases can throw domain specific lower level communication exceptions
            throw new HttpCommunicationException(responseMessage.StatusCode, responseMessage.ReasonPhrase);
    }
}

}

Ok, so now let's write a test to validate that I get my domain specific exception when I request a URL that doesn't exist:

``` C#
[Fact]
public void GettingSomeThingsValueForIdThatDoesntExistThrowsExpectedException()
{
    // Arrange
    var mockHttpMessageHandler = new MockHttpMessageHandler();

    SomeDomainService sut = new SomeDomainService(new HttpClient(mockHttpMessageHandler)
    {
        BaseAddress = new Uri("http://unittest")
    });

    // Act
    Func<Task> action = async () => await sut.GetSomeThingsValueAsync("SomeIdThatDoesntExist");

    // Assert
    action.ShouldThrow<SomeThingDoesntExistException>();
}

Ermahgerd kodenya bahkan lebih sedikit!!$!% Mengapa? Karena saya bahkan tidak perlu mengatur ekspektasi dengan MockHttp dan secara otomatis mengembalikan saya 404 untuk URL yang diminta sebagai perilaku default.

Magic is Real

@PureKrome juga menyebutkan kasus di mana dia ingin membuat instance HttpClient baru dari dalam kelas layanan. Seperti yang saya katakan sebelumnya, dalam hal ini Anda akan mengambil Func<HttpClient> sebagai dependensi alih-alih mengabstraksikan diri Anda dari kreasi yang sebenarnya atau Anda dapat memperkenalkan antarmuka pabrik Anda sendiri jika Anda bukan penggemar Func untuk alasan apa pun.

Saya menghapus balasan saya - saya ingin memikirkan balasan dan kode @drub0y sedikit.

@drub0y Anda lupa menyebutkan bahwa seseorang harus mengetahui bahwa seseorang perlu menerapkan MockHttpMessageHandler di tempat pertama. Itulah inti dari diskusi ini. Itu tidak cukup lurus ke depan. Dengan aspnet5, Anda dapat menguji semuanya hanya dengan mengejek kelas publik dari antarmuka atau kelas dasar dengan metode virtual, tetapi entri API khusus ini berbeda, kurang dapat ditemukan, dan kurang intuitif.

@luisrudge Tentu, saya bisa setuju dengan itu, tetapi bagaimana kami bisa mengetahui bahwa kami membutuhkan Moq atau FakeItEasy? Bagaimana kami mengetahui bahwa kami membutuhkan N/XUnit, FluentAssertions, dll? Saya tidak benar-benar melihat ini sebagai hal yang berbeda.

Ada orang hebat (pintar) di luar sana di komunitas yang menyadari kebutuhan akan hal ini dan benar-benar meluangkan waktu untuk memulai perpustakaan seperti ini untuk memecahkan masalah ini. (Alhamdulillah semuanya!) Kemudian solusi yang sebenarnya "baik" diperhatikan oleh orang lain yang mengenali kebutuhan yang sama dan mulai tumbuh secara organik melalui OSS dan penginjilan oleh para pemimpin komunitas lainnya. Saya pikir segalanya lebih baik dari sebelumnya dalam hal ini dengan kekuatan OSS yang akhirnya direalisasikan di komunitas .NET.

Sekarang, yang mengatakan, saya pribadi percaya bahwa Microsoft seharusnya menyediakan sesuatu _like_ MockHttp bersama dengan pengiriman System.Net.Http API langsung "di luar kotak". Tim AngularJS menyediakan $httpBackEnd bersama dengan $http karena mereka menyadari bahwa orang-orang akan sangat _membutuhkan_ kemampuan ini untuk dapat menguji aplikasi mereka secara efektif. Saya pikir fakta bahwa Microsoft _tidak_ mengidentifikasi masalah yang sama ini adalah mengapa ada begitu banyak "kebingungan" seputar praktik terbaik dengan API ini dan saya pikir mengapa kita semua di sini mendiskusikannya sekarang. :kecewa: Yang mengatakan, saya senang @richardszalay dan @PureKrome telah melangkah ke piring untuk mencoba dan mengisi celah itu dengan perpustakaan mereka dan sekarang saya pikir kita harus mendukung mereka untuk membantu meningkatkan perpustakaan mereka bagaimanapun kita bisa untuk membuat semua hidup kita lebih mudah. :senyum:

Baru saja diberi tahu tentang utas ini, jadi saya pikir saya akan memasukkan 2c saya.

Saya menggunakan kerangka kerja ejekan dinamis sebanyak orang berikutnya, tetapi dalam beberapa tahun terakhir saya mulai menghargai bahwa beberapa domain (seperti HTTP) lebih baik dipalsukan di balik sesuatu dengan lebih banyak pengetahuan domain. Apa pun di luar pencocokan URL dasar akan sangat menyakitkan menggunakan ejekan dinamis, dan saat Anda mengulanginya, Anda akan berakhir dengan versi mini Anda sendiri dari apa yang dilakukan MockHttp.

Ambil Rx sebagai contoh lain: IObservable adalah antarmuka dan karenanya dapat diolok-olok, tetapi akan menjadi kegilaan untuk menguji komposisi kompleks (belum lagi penjadwal) dengan mengejek dinamis saja. Pustaka pengujian Rx memberikan lebih banyak makna semantik ke domain tempat ia bekerja.

Kembali ke HTTP secara khusus, meskipun di dunia JS, Anda benar-benar juga dapat menggunakan sinon untuk mengejek XMLHttpRequest, daripada menggunakan API pengujian Angular, tetapi saya akan sangat terkejut jika ada yang melakukannya.

Setelah mengatakan semua itu, saya sepenuhnya setuju tentang kemampuan untuk ditemukan. Dokumentasi pengujian HTTP Angular ada di sana dengan dokumentasi HTTP-nya, tetapi Anda harus tahu tentang MockHttp untuk mencarinya. Karena itu, saya akan sepenuhnya setuju dengan menyumbangkan MockHttp kembali ke perpustakaan utama, jika ada tim CoreFx yang ingin menghubungi (lisensinya sudah sama).

Saya suka mengejeknya tetapi kami juga menggunakan tanda tangan Func :

public static async Task<T> GetWebObjectAsync<T>(Func<string, Task<string>> getHtmlAsync, string url)
{
    var html = await getHtmlAsync(url);
    var info = JsonConvert.DeserializeObject<T>(html);
    return info;
}

Ini memungkinkan kode kelas Uji menjadi sesederhana ini:

Func<string, Task<string>> getHtmlAsync = u => Task.FromResult(EXPECTED_HTML);
var result = controller.InternalCallTheWeb(getHtmlAsync);

Dan pengontrol cukup melakukan:

public static HttpClient InitHttpClient()
{
    return _httpClient ?? (_httpClient = new HttpClient(new WebRequestHandler
    {
        CachePolicy = new HttpRequestCachePolicy(HttpCacheAgeControl.MaxAge, new TimeSpan(0, 1, 0)),
        Credentials = new NetworkCredential(PublishingCredentialsUserName, PublishingCredentialsPassword)
    }, true));
}

[Route("Information")]
public async Task<IHttpActionResult> GetInformation()
{
    var httpClient = InitHttpClient();
    var result = await InternalCallTheWeb(async u => await httpClient.GetStringAsync(u));
    ...
}

Tapi mereka BISA membuatnya sedikit lebih mudah... :)

Halo tim .NET! Hanya menyentuh dasar pada masalah ini. Sudah ada selama sebulan+ sekarang dan hanya ingin tahu tentang apa yang dipikirkan tim tentang diskusi ini.

Ada berbagai poin dari kedua sisi kubu - semua IMO menarik dan relevan.

Apakah ada update dari kalian gals/guys?

yaitu.

  1. Kami sedang membaca obrolan dan tidak akan mengubah apa pun. Terima kasih atas masukan Anda, semoga hari Anda menyenangkan. Di sini, memiliki sepotong :kue:
  2. Sejujurnya kami belum memikirkannya (segalanya sangat sibuk di sekitar sini, seperti yang bisa Anda bayangkan) _tetapi_ akan memikirkannya nanti _sebelum_ kami merilis Release-To-Web (RTW).
  3. Sebenarnya, kami telah melakukan beberapa diskusi internal tentang hal itu dan kami mengakui bahwa masyarakat menganggap grok sedikit menyakitkan. Jadi kami berpikir kami mungkin melakukan XXXXXXXXX.

ta!

Itu bagian 2 dan 3.

Salah satu cara untuk menyederhanakan kemampuan menyisipkan pengendali "tiruan" untuk membantu pengujian adalah dengan menambahkan metode statis ke HttpClient, yaitu HttpClient.DefaultHandler. Metode statis ini akan memungkinkan pengembang untuk menyetel penangan default yang berbeda dari HttpClientHandler saat ini saat memanggil konstruktor HttpClient() default. Selain membantu pengujian terhadap jaringan "tiruan", ini akan membantu pengembang yang menulis kode ke pustaka portabel di atas HttpClient, di mana mereka tidak secara langsung membuat instance HttpClient. Jadi, itu akan memungkinkan mereka untuk "menyuntikkan" handler default alternatif ini.

Jadi, ini adalah salah satu contoh dari sesuatu yang dapat kita tambahkan ke model objek saat ini yang akan menjadi tambahan dan bukan perubahan besar pada arsitektur saat ini.

Terima kasih banyak @davidsh untuk balasan yang sangat cepat :+1:

Saya akan dengan senang hati menunggu dan menonton ruang ini karena sangat menyenangkan mengetahui bahwa ini bukan bagian 1 :senyum: .. dan berharap untuk melihat perubahan apa pun yang terjadi :+1:

Sekali lagi terima kasih (tim) atas kesabaran dan mendengarkan Anda -- sangat menghargainya!

/me dengan senang hati menunggu.

@davidsh jadi, antarmuka os metode virtual tidak perlu dipertanyakan lagi?

Ini bukan dari pertanyaan. Tetapi penambahan antarmuka jangka pendek seperti yang disarankan dalam utas masalah ini membutuhkan studi dan desain yang cermat. Itu tidak mudah dipetakan ke model objek HttpClient yang ada dan berpotensi akan menjadi perubahan besar pada permukaan API. Jadi, itu bukan sesuatu yang cepat atau mudah untuk dirancang/diimplementasikan.

Bagaimana dengan metode virtual?

Ya, menambahkan metode virtual bukanlah perubahan yang merusak tetapi tambahan. Kami masih mencari tahu perubahan desain apa yang layak dilakukan di area ini.

/me melemparkan Resurrection ke utas ini...

~ tikungan benang, erangan, twiches dan menjadi hidup!


Baru saja mendengarkan StandUp Komunitas 20 Agustus 2015 dan mereka menyebutkan bahwa HttpClient akan tersedia lintas platform dengan Beta7. YA!

Jadi ... sudah ada keputusan tentang ini? Tidak menyarankan baik/atau .. hanya dengan sopan meminta pembaruan diskusi, dll?

@PureKrome Belum ada keputusan tentang ini tetapi pekerjaan desain/investigasi API ini digabungkan ke dalam rencana masa depan kami. Saat ini, sebagian besar dari kita sangat fokus untuk mendapatkan sisa kode System.Net ke GitHub dan bekerja lintas platform. Ini termasuk kode sumber yang ada dan porting pengujian kami ke kerangka kerja Xunit. Setelah upaya ini menyatu, tim akan memiliki lebih banyak waktu untuk mulai berfokus pada perubahan di masa mendatang seperti yang disarankan di utas ini.

Terima kasih banyak @davidsh - Saya sangat menghargai waktu Anda untuk membalas :) Pertahankan pekerjaan yang luar biasa! :balon:

Bukankah menggunakan ejekan untuk menguji layanan http agak usang? Ada beberapa opsi hosting mandiri yang memungkinkan ejekan mudah terhadap layanan http dan tidak memerlukan perubahan kode, hanya perubahan dalam url layanan. Yaitu HttpListener dan OWIN self-host, tetapi untuk API yang kompleks, seseorang bahkan dapat membangun layanan tiruan atau menggunakan penjawab otomatis.

image

image

Adakah pembaruan pada kemampuan pengujian unit dari kelas HttpClient ini? Saya mencoba untuk mengejek metode DeleteAsync tetapi seperti yang disebutkan sebelumnya, Moq mengeluh tentang fungsi yang tidak virtual. Sepertinya tidak ada cara yang lebih mudah selain membuat pembungkus kita sendiri.

Saya memilih Richardszalay.Mockhttp!
Ini sangat bagus! Sederhana dan brilian!

https://github.com/richardszalay/mockhttp

tapi @YehudahA - kita tidak perlu itu.... (itulah inti dari obrolan ini) :)

@PureKrome kita tidak perlu IHttpClient .
Idenya sama dengan EF 7. Anda tidak pernah menggunakan IDbContext atau IDbSet, tetapi Anda hanya mengubah perilaku menggunakan DbContextOptions..

Richardszalay.MockHttp adalah solusi out-of-the-box.

Ini adalah solusi out-of-another-box.

kita tidak perlu IHttpClient.
Idenya sama dengan EF 7. Anda tidak pernah menggunakan IDbContext atau IDbSet, tetapi Anda hanya mengubah perilaku menggunakan DbContextOptions.

Tidak apa-apa - saya sangat tidak setuju, jadi kami setuju untuk tidak setuju.

Apakah kedua cara itu berhasil? Ya. Beberapa orang (seperti saya) menganggap mengejek antarmuka atau metode virtual LEBIH MUDAH karena alasan seperti dapat ditemukan dan dibaca serta disederhanakan.

Orang lain (seperti Anda) suka memanipulasi perilaku. Saya menemukan bahwa lebih kompleks, lebih sulit untuk menemukan dan lebih memakan waktu. (yang (bagi saya) diterjemahkan menjadi lebih sulit untuk didukung/dipertahankan).

Masing-masing untuk mereka sendiri, saya kira :)

Richardszalay mengatakan: "Pola ini sangat terinspirasi oleh $httpBackend" AngularJS.
Tepat sekali. Mari lihat di sini: https://docs.angularjs.org/api/ngMock/service/ $httpBackend

@PureKrome
Ya itu lebih sederhana.
Anda tidak perlu tahu kelas dan metode apa yang digunakan, Anda tidak memerlukan perpustakaan tiruan/antarmuka/metode virtual, pengaturan tidak akan berubah jika Anda beralih dari HttpClient ke yang lain.

        private static IDisposable FakeService(string uri, string response)
        {
            var httpListener = new HttpListener();
            httpListener.Prefixes.Add(uri);

            httpListener.Start();

            httpListener.GetContextAsync().ContinueWith(task =>
            {
                var context = task.Result;

                var buffer = Encoding.UTF8.GetBytes(response);

                context.Response.OutputStream.Write(buffer, 0, buffer.Length);

                context.Response.OutputStream.Close();
            });

            return httpListener;
        }

Penggunaan:

            var uri = "http://localhost:8888/myservice/";
            var fakeResponse = "Hello World";

            using (FakeService(uri, fakeResponse))
            {
                // Run your test here ...

                // This is only to test that is really working:
                using (var client = new HttpClient())
                {
                    var result = client.GetStringAsync(uri).Result;
                }
            }

@metzgithub adalah yang terbaik di sini, karena memungkinkan simulasi yang tepat dari perilaku layanan target.

Persis seperti yang saya cari karena memberi saya tempat yang sempurna untuk meletakkan data 'mal-formed/malicious' (untuk tes keamanan dan non-happy-path)

Saya terlambat ke utas ini, saya tiba di sini setelah mencari untuk melihat bagaimana orang menguji ketergantungan mereka pada HttpClient, dan menemukan beberapa cara yang sangat inventif untuk melakukannya. Saya akan menyatakan di depan bahwa dalam argumen ini saya akan turun dengan tegas di samping karena memiliki antarmuka. Namun gosok bagi saya adalah bahwa saya tidak punya pilihan.

Saya memilih untuk merancang dan mengembangkan komponen dan layanan yang menyatakan ketergantungannya pada komponen lain. Saya sangat lebih suka pilihan untuk mendeklarasikan dependensi tersebut melalui konstruktor dan pada saat run time mengandalkan wadah IoC untuk menyuntikkan implementasi dan kontrol konkret pada titik ini apakah itu contoh tunggal atau sementara.

Saya memilih untuk menggunakan definisi testabilitas yang saya pelajari sekitar satu dekade yang lalu sambil merangkul praktik Pengembangan Berbasis Tes yang relatif baru:

"Sebuah kelas dapat diuji jika dapat dihapus dari lingkungan normalnya dan masih berfungsi ketika semua dependensinya disuntikkan"

Pilihan lain yang lebih saya sukai adalah menguji interaksi komponen saya dengan komponen yang mereka andalkan. Jika saya mendeklarasikan ketergantungan pada suatu komponen, saya ingin menguji apakah komponen saya menggunakannya dengan benar.

IMHO API yang dirancang dengan baik memungkinkan saya untuk bekerja dan membuat kode dengan cara yang saya pilih, beberapa kendala teknis dapat diterima. Saya melihat hanya memiliki implementasi konkret dari komponen seperti HttpClient sebagai merampas pilihan saya.

Jadi saya memiliki pilihan untuk menggunakan solusi saya yang biasa dalam kasus ini dan membuat perpustakaan adaptor/pembungkus ringan yang memberi saya antarmuka dan memungkinkan saya untuk bekerja dengan cara yang saya pilih.

Saya juga agak terlambat ke pesta, tetapi bahkan setelah membaca semua komentar sebelumnya, saya masih tidak yakin bagaimana melanjutkannya. Bisakah Anda memberi tahu saya bagaimana saya akan memverifikasi bahwa metode tertentu di objek saya yang sedang diuji memanggil titik akhir tertentu pada API publik? Saya tidak peduli apa yang kembali. Saya hanya ingin memastikan bahwa ketika seseorang memanggil "GoGetSomething" pada turunan dari kelas ApiClient saya, itu mengirimkan GET ke " https://somenifty.com/api/something ". Yang ingin saya lakukan adalah (bukan kode yang berfungsi):

Mock<HttpClient> mockHttpClient = new Mock<HttpClient>();
mockHttpClient.Setup(x => x.SendAsync(It.IsAny<HttpRequestMessage>())).Verifiable();
ApiClient client = new ApiClient(mockHttpClient.Object);

Something response = await client.GoGetSomething();
mockHttpClient
    .Verify(x => x.SendAsync(
        It.Is<HttpRequestMessage>(m =>
            m.Method.Equals(HttpMethod.Get) &&
            m.RequestUri.Equals("https://somenifty.com/api/something"))));

Tapi, itu tidak membiarkan saya mengejek SendAsync.

JIKA saya bisa membuat hal dasar itu berfungsi, maka saya mungkin ingin benar-benar menguji bagaimana kelas saya menangani PENGEMBALIAN tertentu dari panggilan itu juga.

Ada ide? Ini sepertinya (bagi saya) tidak terlalu sulit untuk diuji.

Tapi, itu tidak membiarkan saya mengejek SendAsync.

Anda tidak mengejek metode HttpClient.SendAsync .

Alih-alih, Anda membuat penangan tiruan yang berasal dari HttpMessageHandler . Kemudian Anda mengganti metode SendAsync itu. Kemudian Anda meneruskan objek handler itu ke konstruktor HttpClient .

@jdcrutchley , Anda dapat melakukannya dengan MockHttp @richardszalay .

public class MyApiClient
{
    private readonly HttpClient httpClient;

    public MyApiClient(HttpMessageHandler handler)
    {
        httpClient = new HttpClient(handler, disposeHandler: false);
    }

    public async Task<HttpStatusCode> GoGetSomething()
    {
        var response = await httpClient.GetAsync("https://somenifty.com/api/something");
        return response.StatusCode;
    }
}

[TestMethod]
public async Task MyApiClient_GetSomething_Test()
{
    // Arrange
    var mockHandler = new MockHttpMessageHandler();
    mockHandler.Expect("https://somenifty.com/api/something")
        .Respond(HttpStatusCode.OK);

    var client = new MyApiClient(mockHandler);

    // Act
    var response = await client.GoGetSomething();

    // Assert
    Assert.AreEqual(response, HttpStatusCode.OK);
    mockHandler.VerifyNoOutstandingExpectation();
}

@jdcrutchley atau Anda membuat kelas pembungkus dan antarmuka pembungkus SENDIRI dengan satu metode SendAsync .. yang hanya memanggil _real_ httpClient.SendAsync (di kelas pembungkus Anda).

Saya kira itulah yang dilakukan MockHttp @richardszalay .

Itulah pola umum yang dilakukan orang ketika mereka tidak dapat mengejek sesuatu (karena tidak ada antarmuka ATAU tidak virtual).

mockhttp adalah DSL tiruan di HttpMessageHandler. Tidak ada bedanya dengan mengejeknya secara dinamis menggunakan Moq, tetapi maksudnya lebih jelas karena DSL.

Pengalamannya sama: tiruan HttpMessageHandler dan buat HttpClient konkret darinya. Komponen domain Anda bergantung pada HttpClient atau HttpMessageHandler.

Sunting: TBH, saya tidak sepenuhnya yakin apa masalah dengan desain yang ada. Perbedaan antara "mockable HttpClient" dan "mockable HttpMessageHandler" secara harfiah adalah satu baris kode dalam pengujian Anda: new HttpClient(mockHandler) , dan memiliki keuntungan bahwa implementasi mempertahankan desain yang sama untuk "Implementasi berbeda per platform" dan " Implementasi yang berbeda untuk pengujian" (yaitu tidak tercemar untuk tujuan kode pengujian).

Keberadaan perpustakaan seperti MockHttp hanya berfungsi untuk mengekspos API pengujian yang lebih bersih dan spesifik domain. Membuat HttpClient dapat diejek melalui antarmuka tidak akan mengubah ini.

Sebagai pendatang baru di HttpClient API, semoga pengalaman saya menambah nilai diskusi.

Saya juga terkejut mengetahui bahwa HttpClient tidak mengimplementasikan antarmuka untuk ejekan yang mudah. Saya mencari di seluruh web, menemukan utas ini, dan membacanya dari awal hingga akhir sebelum melanjutkan. Jawaban dari @drub0y meyakinkan saya untuk melanjutkan dengan hanya mengejek HttpMessageHandler.

Kerangka pilihan saya yang mengejek adalah Moq, mungkin cukup standar untuk .NET devs. Setelah menghabiskan hampir satu jam melawan Moq API dengan mencoba mengatur dan memverifikasi metode SendAsync yang dilindungi, saya akhirnya menemukan bug di Moq dengan memverifikasi metode generik yang dilindungi. Lihat di sini .

Saya memutuskan untuk membagikan pengalaman ini untuk mendukung argumen bahwa antarmuka vanilla IHttpClient akan membuat hidup lebih mudah dan menyelamatkan saya beberapa jam. Sekarang saya menemui jalan buntu, Anda dapat menambahkan saya ke daftar pengembang yang telah menulis kombo pembungkus/antarmuka sederhana di sekitar HttpClient.

Itu poin yang bagus Ken, saya lupa bahwa HttpMessageHandler.SendAsync dilindungi.

Namun, saya tetap menghargai bahwa desain API difokuskan pada ekstensibilitas platform terlebih dahulu, dan tidak sulit untuk membuat subkelas HttpMessageHandler (lihat mockhttp sebagai contoh). Bahkan bisa menjadi subkelas yang mengalihkan panggilan ke metode publik abstrak yang lebih ramah-Mock. Saya menyadari bahwa tampaknya "tidak murni", tetapi Anda dapat memperdebatkan testibility vs tainting-API-surface-for-testing-purposes selamanya.

+1 untuk antarmuka.

Seperti yang lain, saya pendatang baru di HttpClient dan membutuhkannya di kelas, mulai mencari cara menguji unit, menemukan utas ini - dan sekarang beberapa jam kemudian saya secara efektif harus mempelajari detail implementasinya untuk mengujinya. Fakta bahwa begitu banyak orang harus melalui jalan ini adalah bukti bahwa ini adalah masalah nyata bagi orang-orang. Dan dari sedikit yang telah angkat bicara, berapa banyak lagi yang hanya membaca utas dalam diam?

Saya sedang memimpin proyek besar di perusahaan saya dan tidak punya waktu untuk mengejar jejak kelinci semacam ini. Sebuah antarmuka akan menghemat waktu saya, uang perusahaan saya - dan itu mungkin sama untuk banyak orang lain.

Saya mungkin akan menempuh rute membuat pembungkus saya sendiri. Karena melihat dari luar, tidak masuk akal bagi siapa pun mengapa saya menyuntikkan HttpMessageHandler ke kelas saya.

Jika tidak mungkin membuat antarmuka untuk HttpClient - maka mungkin tim .net dapat membuat solusi http baru yang memiliki antarmuka?

@scott-martin, HttpClient tidak melakukan apa-apa, dia hanya mengirim permintaan ke HttpMessageHandler .

Alih-alih menguji HttpClient Anda dapat menguji dan mengejek HttpMessageHandler . Ini sangat sederhana, dan Anda juga dapat menggunakan richardszalay mockhttp .

@scott-martin seperti yang disebutkan hanya beberapa komentar, Anda tidak menyuntikkan HttpMessageHandler. Anda menyuntikkan HttpClient, dan menyuntikkan _that_ dengan HttpMessageHandler saat Anda menguji unit.

@richardszalay terima kasih atas tipnya, itu berfungsi lebih baik dalam menjaga abstraksi tingkat rendah dari implementasi saya.

@YehudahA , saya bisa membuatnya bekerja dengan mengejek HttpMessageHandler. Tapi "menjadikannya bekerja" bukanlah alasan saya menimpali. Seperti yang dikatakan @PureKrome , ini adalah masalah kemampuan menemukan. Jika ada antarmuka, saya akan bisa mengejeknya dan dalam beberapa menit. Karena tidak ada, saya harus menghabiskan beberapa jam untuk mencari solusi.

Saya memahami keterbatasan dan keragu-raguan untuk menyediakan antarmuka. Saya hanya ingin memberikan umpan balik saya sebagai anggota komunitas yang kami suka antarmuka dan tolong beri kami lebih banyak - mereka jauh lebih mudah untuk berinteraksi dan menguji.

ini adalah masalah kemampuan menemukan. Jika ada antarmuka, saya akan dapat mengejeknya dan siap dalam beberapa menit . Karena tidak ada, saya harus menghabiskan beberapa jam untuk mencari solusi.

:arrow_up: ini. Ini semua tentang, ini.

EDIT: penekanan, milikku.

Meskipun saya sepenuhnya setuju bahwa itu bisa lebih mudah ditemukan, saya tidak yakin bagaimana itu bisa memakan waktu "beberapa jam". Googling "mock httpclient" memunculkan pertanyaan Stack Overflow ini sebagai hasil pertama, di mana dua jawaban berperingkat tertinggi (meskipun, diberikan, bukan jawaban yang diterima) menggambarkan HttpMessageHandler.

NP - kami masih setuju untuk tidak setuju. Ini semua baik :)

Anda dapat menutup masalah ini, karena mscorlib akan kembali dengan semua barang bawaannya :)

Bisakah kita meminta kelas httpclient ini mengimplementasikan antarmuka? Kemudian bisa menangani sisanya sendiri.

Solusi mana yang Anda usulkan untuk mengejek HttpClient yang digunakan dalam Layanan DI?
Saya mencoba menerapkan FakeHttpClientHandler dan saya tidak tahu apa yang harus dilakukan dengan metode SendAsync.

Saya memiliki GeoLocationService sederhana yang memanggil layanan mikro untuk mengambil informasi lokasi berbasis ip dari yang lain. Saya ingin meneruskan HttpClientHandler palsu ke layanan, yang memiliki dua konstruktor, satu menerima locationServiceAddress dan satu dengan locationServiceAddress dan HttpClientHandler tambahan.

public class GeoLocationService: IGeoLocationService {

        private readonly string _geoLocationServiceAddress;
        private HttpClient _httpClient;
        private readonly object _gate = new object();

        /// <summary>
        /// Creates a GeoLocation Service Instance for Dependency Injection
        /// </summary>
        /// <param name="locationServiceAddress">The GeoLocation Service Hostname (IP Address), including port number</param>
        public GeoLocationService(string locationServiceAddress) {
            // Add the ending slash to be able to GetAsync with the ipAddress directly
            if (!locationServiceAddress.EndsWith("/")) {
                locationServiceAddress += "/";
            }

            this._geoLocationServiceAddress = locationServiceAddress;
            this._httpClient = new HttpClient {
                BaseAddress = new Uri(this._geoLocationServiceAddress)
            };
        }

        /// <summary>
        /// Creates a GeoLocation Service Instance for Dependency Injection (additional constructor for Unit Testing the Service)
        /// </summary>
        /// <param name="locationServiceAddress">The GeoLocation Service Hostname (IP Address), including port number.</param>
        /// <param name="clientHandler">The HttpClientHandler for the HttpClient for mocking responses in Unit Tests.</param>
        public GeoLocationService(string locationServiceAddress, HttpClientHandler clientHandler): this(locationServiceAddress) {
            this._httpClient.Dispose();
            this._httpClient = new HttpClient(clientHandler) {
                BaseAddress = new Uri(this._geoLocationServiceAddress)
            };
        }

        /// <summary>
        /// Geo Location Microservice Http Call with recreation of HttpClient in case of failure
        /// </summary>
        /// <param name="ipAddress">The ip address to locate geographically</param>
        /// <returns>A <see cref="string">string</see> representation of the Json Location Object.</returns>
        private async Task<string> CallGeoLocationServiceAsync(string ipAddress) {
            HttpResponseMessage response;
            string result = null;

            try {
                response = await _httpClient.GetAsync(ipAddress);

                response.EnsureSuccessStatusCode();

                result = await response.Content.ReadAsStringAsync();
            }
            catch (Exception ex) {
                lock (_gate) {
                    _httpClient.Dispose(); 
                    _httpClient = new HttpClient {
                        BaseAddress = new Uri(this._geoLocationServiceAddress)
                    };
                }

                result = null;

                Logger.LogExceptionLine("GeoLocationService", "CallGeoLocationServiceAsync", ex.Message, stacktrace: ex.StackTrace);
            }

            return result;
        }

        /// <summary>
        /// Calls the Geo Location Microservice and returns the location description for the provided IP Address. 
        /// </summary>
        /// <param name="ipAddress">The <see cref="string">IP Address</see> to be geographically located by the GeoLocation Microservice.</param>
        /// <returns>A <see cref="string">string</see> representing the json object location description. Does two retries in the case of call failure.</returns>
        public async Task<string> LocateAsync(string ipAddress) {
            int noOfRetries = 2;
            string result = "";

            for (int i = 0; i < noOfRetries; i ++) {
                result = await CallGeoLocationServiceAsync(ipAddress);

                if (result != null) {
                    return result;
                }
            }

            return String.Format(Constants.Location.DefaultGeoLocationResponse, ipAddress);
        }

        public void Dispose() {
            _httpClient.Dispose();
        }

    }

Di kelas uji, saya ingin menulis kelas internal bernama FakeClientHandler yang memperluas HttpClientHandler, yang menggantikan Metode SendAsync. Metode lain, saya kira, akan menggunakan kerangka kerja tiruan untuk mengejek HttpClientHandler. Saya belum tahu bagaimana melakukannya. Jika Anda bisa membantu, saya akan selamanya berhutang budi kepada Anda.

public class GeoLocationServiceTests {

        private readonly IConfiguration _configuration;
        private IGeoLocationService _geoLocationService;
        private HttpClientHandler _clientHandler;

        internal class FakeClientHandler : HttpClientHandler {

            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {

                // insert implementation here

                // insert fake responses here. 
                return (Task<HttpResponseMessage>) null;
            }

        }

        public GeoLocationServiceTests() {
            this._clientHandler = new FakeClientHandler();
            this._clientHandler.UseDefaultCredentials = true;
            this._geoLocationService = new GeoLocationService("http://fakegeolocation.com/json/", this._clientHandler);
        }


        [Fact]
        public async void RequestGeoLocationFromMicroservice() {
            string ipAddress = "192.36.1.252";

            string apiResponse = await this._geoLocationService.LocateAsync(ipAddress);
            Dictionary<string, string> result = JsonConvert.DeserializeObject<Dictionary<string,string>>(apiResponse);

            Assert.True(result.ContainsKey("city"));

            string timeZone;
            result.TryGetValue("time_zone", out timeZone);

            Assert.Equal(timeZone, @"Europe/Stockholm");
        }

        [Fact]
        public async void RequestBadGeoLocation() {
            string badIpAddress = "asjldf";

            string apiResponse = await this._geoLocationService.LocateAsync(badIpAddress);
            Dictionary<string, string> result = JsonConvert.DeserializeObject<Dictionary<string, string>>(apiResponse);

            Assert.True(result.ContainsKey("city"));

            string city;
            result.TryGetValue("city", out city);
            Assert.Equal(city, "NA");

            string ipAddress;
            result.TryGetValue("ip", out ipAddress);
            Assert.Equal(badIpAddress, ipAddress);
        }
    }

@danielmihai HttpMessageHandler secara efektif adalah "implementasi". Anda biasanya membuat HttpClient, meneruskan handler ke konstruktor, dan kemudian membuat lapisan layanan Anda bergantung pada HttpClient.

Menggunakan kerangka kerja mengejek umum sulit karena SendAsync yang dapat diganti dilindungi. Saya menulis mockhttp khusus untuk alasan itu (tetapi juga karena senang memiliki fitur mengejek khusus domain.

@danielmihai Apa yang Richard katakan cukup banyak meringkas _pain_ yang kita semua harus tanggung sekarang dengan mengejek HttpClient.

Ada juga HttpClient.Helpers sebagai _another_ library yang membantu Anda mengolok-olok HttpClient .. atau lebih khusus lagi, memberikan HttpMessageHandler palsu agar Anda memalsukan tanggapan.

Saya sudah menemukan jawabannya, menulis sesuatu seperti ini... [Saya pikir saya sudah berurusan dengan 'rasa sakit']

internal class FakeClientHandler : HttpClientHandler {
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
                if (request.Method == HttpMethod.Get) {
                    if (request.RequestUri.PathAndQuery.Contains("/json/")) {
                        string requestPath = request.RequestUri.PathAndQuery;
                        string[] splitPath = requestPath.Split(new char[] { '/' });
                        string ipAddress = splitPath.Last();

                        // RequestGeoLocationFromMicroservice
                        if (ipAddress.Equals(ipAddress1)) {
                            var response = new HttpResponseMessage(HttpStatusCode.OK);
                            response.Content = new StringContent(response1);

                            return Task.FromResult(response);
                        }

                        // RequestBadGeoLocation
                        if (ipAddress.Equals(ipAddress2)) {
                            var response = new HttpResponseMessage(HttpStatusCode.OK);
                            response.Content = new StringContent(response2);
                            return Task.FromResult(response);
                        }
                    }
                }
                return (Task<HttpResponseMessage>) null;
            }
        }

Semua tes lulus

Silakan beri komentar tentang implementasinya, jika ada cara yang lebih baik untuk melakukannya.

Terima kasih!

Menambahkan pemikiran saya ke diskusi lebih dari satu tahun ini.

@SidharthNabar

Pertama, saya perhatikan bahwa Anda membuat contoh baru HttpClient untuk mengirim setiap permintaan - itu bukan pola desain yang dimaksudkan untuk HttpClient. Membuat satu instans HttpClient dan menggunakannya kembali untuk semua permintaan Anda membantu mengoptimalkan kumpulan koneksi dan manajemen memori. Harap pertimbangkan untuk menggunakan kembali satu instance HttpClient. Setelah Anda melakukan ini, Anda dapat memasukkan handler palsu ke dalam satu instance HttpClient dan selesai.

Di mana saya membuang instance yang tidak disuntikkan ketika saya tidak seharusnya menggunakan pernyataan using , tetapi new meningkatkan instance HttpClient saya, misalnya di konstruktor konsumen? Bagaimana dengan HttpMessageHandler yang saya buat untuk disuntikkan ke dalam instance HttpClient?

Selain antarmuka-atau-tidak-diskusi yang lengkap (saya mendukung antarmuka), setidaknya dokumen dapat menyatakan bahwa Anda dapat menyuntikkan HttpMessageHandler untuk tujuan pengujian. Ya, konstruktor disebutkan, ya, disebutkan bahwa Anda dapat menyuntikkan HttpMessageHandler Anda sendiri, tetapi tidak disebutkan mengapa saya ingin melakukan ini.

@richardszalay

Meskipun saya sepenuhnya setuju bahwa itu bisa lebih mudah ditemukan, saya tidak yakin bagaimana itu bisa memakan waktu "beberapa jam". Googling "mock httpclient" memunculkan pertanyaan Stack Overflow ini sebagai hasil pertama, di mana dua jawaban berperingkat tertinggi (meskipun, diberikan, bukan jawaban yang diterima) menggambarkan HttpMessageHandler.

API harus intuitif untuk digunakan dengan menelusuri dokumen, IMHO. Harus mengajukan pertanyaan kepada Google atau SO tentu saja tidak intuitif, pertanyaan yang ditautkan kembali ke cara sebelum .NET Core membuat pembaruan tidak jelas (mungkin ada sesuatu yang berubah di antara pertanyaan dan rilis kerangka kerja _baru_?)

Terima kasih kepada semua orang yang telah menambahkan contoh kode mereka dan menyediakan lib pembantu!

Maksud saya, jika kita memiliki kelas yang perlu membuat panggilan http, dan kita ingin menguji unit kelas itu, maka kita harus bisa mengejek klien http itu. Karena klien http tidak dihubungkan, saya tidak dapat menyuntikkan tiruan untuk klien itu. Sebagai gantinya, saya harus membuat pembungkus (yang mengimplementasikan antarmuka) untuk klien http yang menerima penangan pesan sebagai parameter opsional, dan kemudian mengirimkannya penangan palsu dan menyuntikkannya. Itu banyak sekali rintangan ekstra untuk dilewati tanpa biaya. Alternatifnya adalah meminta kelas saya mengambil turunan dari HttpMessageHandler alih-alih HttpClient, saya kira ...

Saya tidak bermaksud membelah rambut, itu hanya terasa canggung dan sepertinya agak tidak intuitif. Saya pikir fakta bahwa ini adalah topik yang sangat populer mendukung hal itu.

Karena klien http tidak dihubungkan, kami tidak dapat menyuntikkannya dengan wadah.

@dasjestyr Kami dapat melakukan injeksi ketergantungan tanpa antarmuka. Antarmuka memungkinkan pewarisan berganda; mereka bukan persyaratan untuk injeksi ketergantungan.

Ya, DI bukan masalahnya di sini. Betapa mudah/menyenangkan untuk mengejeknya, dll.

@shaunluttin Saya sadar bahwa kita dapat melakukan DI tanpa antarmuka, saya tidak menyiratkan sebaliknya; Saya berbicara tentang kelas yang memiliki ketergantungan pada HttpClient. Tanpa antarmuka untuk klien, saya tidak dapat menyuntikkan klien http tiruan atau klien http apa pun (sehingga dapat diuji secara terpisah tanpa membuat panggilan http yang sebenarnya) kecuali saya membungkusnya dalam adaptor yang mengimplementasikan antarmuka yang saya ' telah didefinisikan untuk meniru HttpClient. Saya telah mengubah beberapa kata sedikit untuk kejelasan jika Anda terpaku pada satu kalimat.

Sementara itu, saya telah membuat penangan pesan palsu di unit test saya dan menyuntikkan itu yang berarti kelas saya dirancang untuk menyuntikkan penangan yang saya kira bukan hal terburuk di dunia tetapi rasanya canggung. Dan tes membutuhkan palsu alih-alih ejekan/rintisan. Bruto.

Juga, berbicara secara akademis, antarmuka tidak memungkinkan pewarisan berganda, mereka hanya memungkinkan Anda untuk menirunya. Anda tidak mewarisi antarmuka, Anda mengimplementasikannya -- Anda tidak _mewarisi_ fungsionalitas apa pun dengan mengimplementasikan antarmuka, hanya menyatakan bahwa Anda telah mengimplementasikan fungsionalitas dalam beberapa cara. Untuk meniru pewarisan berganda dan beberapa fungsi yang sudah ada yang telah Anda tulis di tempat lain, Anda dapat mengimplementasikan antarmuka dan menyalurkan implementasi ke anggota internal yang memiliki fungsi sebenarnya (misalnya adaptor) yang sebenarnya adalah komposisi, bukan pewarisan.

Sementara itu, saya telah membuat penangan pesan palsu di unit test saya dan menyuntikkan itu yang berarti kelas saya dirancang untuk menyuntikkan penangan yang saya kira bukan hal terburuk di dunia tetapi rasanya canggung. Dan tes membutuhkan palsu alih-alih ejekan/rintisan. Bruto.

Ini adalah inti/inti dari masalah ini -> dan juga _murni_ berpendirian.

Satu sisi pagar: penangan pesan palsu karena <insert their rational here>
Sisi lain dari pagar: antarmuka (atau bahkan metode virtual, tapi itu masih sedikit PITA) karena <insert their rational here> .

Maksud saya palsu membutuhkan banyak pekerjaan ekstra untuk disiapkan dan dalam banyak kasus mengakibatkan Anda menulis seluruh kelas lain hanya untuk tes yang tidak benar-benar menambah banyak nilai. Rintisan sangat bagus karena memungkinkan pengujian berlanjut tanpa implementasi nyata. Mock adalah hal yang sangat saya minati karena dapat digunakan untuk pernyataan. Misalnya, nyatakan bahwa metode pada implementasi antarmuka ini dipanggil sebanyak x kali dengan penyiapan pengujian ini saat ini.

Saya benar-benar harus mengujinya di salah satu kelas saya. Untuk melakukannya, saya harus menambahkan akumulator ke handler palsu yang bertambah setiap kali metode SendAsync() dipanggil. Itu adalah kasus sederhana, untungnya, tapi ayolah. Mengolok-olok praktis menjadi standar pada saat ini.

Yap - sangat setuju dengan Anda di sini 👍

Tolong, Microsoft, tambahkan IHttpClient , titik. ATAU konsisten dan hapus semua antarmuka Anda di BCL - IList , IDictionary , ICollection , karena tetap "sulit untuk versi". Heck, singkirkan juga IEnumerable .

Pada catatan serius, daripada berdebat seperti "Anda bisa menulis handler Anda sendiri" (tentu saja, tetapi antarmuka lebih mudah dan bersih) dan "antarmuka memperkenalkan perubahan yang melanggar" (yah, Anda tetap melakukannya di semua versi .net) , cukup TAMBAHKAN ANTARMUKA, dan biarkan pengembang memutuskan bagaimana mereka ingin mengujinya.

@lasseschou IList , IDictionary , ICollection dan IEnumerable sangat penting karena banyaknya variasi kelas yang mengimplementasikannya. IHttpClient hanya akan diimplementasikan oleh HttpClient - agar konsisten dengan logika Anda, setiap kelas dalam BCL harus diberikan tipe antarmuka karena kita mungkin perlu menirunya.
Saya akan sedih melihat kekacauan seperti ini.

Kecuali ada tradeoff yang dramatis, IHttpClient adalah level abstraksi yang salah untuk diolok-olok. Aplikasi Anda hampir pasti tidak akan menjalankan seluruh kemampuan HttpClient. Saya menyarankan agar Anda membungkus HttpClient dalam layanan Anda sendiri dalam hal permintaan khusus aplikasi Anda dan mengejek layanan Anda sendiri.

@jnm2 - Karena minat, mengapa menurut Anda IHttpClient adalah level abstraksi yang salah untuk diejek?

Saya tidak membenci atau menjelek-jelekkan - jujur ​​Q.

(Suka belajar, dll).

Saya akan menanyakan hal yang sama mengingat ini adalah tingkat abstraksi yang cukup sempurna mengingat ini adalah klien.

Itu tidak mutlak. Jika Anda membuat browser web, saya dapat melihat perbandingan 1:1 antara apa yang dibutuhkan aplikasi Anda dan apa yang abstrak HttpClient. Tetapi jika Anda menggunakan HttpClient untuk berbicara dengan semacam API - apa pun dengan harapan yang lebih spesifik di atas HTTP - Anda hanya perlu sebagian kecil dari apa yang dapat dilakukan HttpClient. Jika Anda membangun layanan yang merangkum mekanisme transport HttpClient dan mengejek layanan itu, aplikasi Anda lainnya dapat mengkodekan terhadap abstraksi antarmuka khusus API layanan Anda, daripada mengkodekan terhadap HttpClient.

Juga, saya berlangganan prinsip "Jangan pernah mengejek tipe yang tidak Anda miliki", jadi YMMV.
Lihat juga http://martinfowler.com/articles/mocksArentStubs.html membandingkan pengujian klasik vs mengejek.

Intinya di sini adalah bahwa HttpClient adalah kemajuan yang luar biasa
WebClient, XMLHttpRequest, yang menjadi cara yang disukai untuk melakukan HTTP
panggilan. Sangat bersih dan modern. Saya selalu menggunakan adaptor mockable yang berbicara
langsung ke HttpClient, jadi menguji kode saya itu mudah. Tapi aku ingin
uji kelas adaptor itu juga. Dan saat itulah saya menemukan: tidak ada
Klien IHttp. Mungkin Anda benar, itu tidak diperlukan. Mungkin menurutmu itu
tingkat abstraksi yang salah. Tapi itu akan sangat nyaman bukan
harus membungkusnya di kelas lain di semua proyek. Saya hanya tidak melihat
alasan apa pun untuk tidak memiliki antarmuka terpisah untuk kelas yang begitu penting.

Lasse Schou
Pendiri & CEO

http://mouseflow.com
[email protected]

https://www.facebook.com/mouseflowdotcom/
https://twitter.com/mouseflow
https://www.linkedin.com/company/mouseflow

PEMBERITAHUAN KERAHASIAAN: Pesan email ini, termasuk lampiran apa pun, adalah
untuk penggunaan tunggal penerima yang dituju dan mungkin berisi rahasia,
hak milik, dan/atau informasi istimewa yang dilindungi oleh hukum. Setiap
tinjauan, penggunaan, pengungkapan, atau distribusi yang tidak sah dilarang. Jika kamu
bukan penerima yang dituju, harap hubungi pengirim melalui email balasan
dan hancurkan semua salinan dari pesan asli.

Pada 12 November 2016 pukul 14:57, Joseph Musser [email protected]
menulis:

Itu tidak mutlak. Jika Anda sedang membangun browser web, maka saya bisa melihat
bagaimana 1:1 antara apa yang dibutuhkan aplikasi Anda dan apa yang abstrak HttpClient. Tetapi
jika Anda menggunakan HttpClient untuk berbicara dengan semacam API- apa pun dengan
harapan yang lebih spesifik di atas HTTP, Anda hanya perlu a
sebagian kecil dari apa yang dapat dilakukan HttpClient. Jika Anda membangun layanan yang
merangkum mekanisme transportasi HttpClient dan mengejek layanan itu, the
aplikasi Anda yang lain dapat mengkodekan antarmuka khusus API layanan Anda
abstraksi, daripada coding terhadap HttpClient.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/dotnet/corefx/issues/1624#issuecomment -260123552, atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/ACnGPp3fSriEJCAjXeAqMZbBcTWcRm9Rks5q9cXlgaJpZM4EPBHy
.

Saya mendengar beberapa masukan aneh bahwa HttpClient "terlalu fleksibel" untuk menjadi lapisan yang tepat untuk diejek -- Itu lebih seperti efek samping dari keputusan untuk memiliki satu kelas yang memiliki begitu banyak tanggung jawab dan fitur yang terbuka. Tapi itu benar-benar tidak tergantung pada bagaimana orang _ingin_ menguji kode.

Saya masih sangat berharap kami memperbaiki ini, jika hanya karena kami melanggar enkapsulasi dengan mengharapkan konsumen mengetahui detail ini tentang bagaimana HttpClient diimplementasikan (Dalam hal ini, untuk "mengetahui" bahwa itu adalah pembungkus yang tidak menambahkan fungsionalitas yang berarti dan membungkus kelas lain yang mungkin atau mungkin tidak mereka gunakan). Konsumen kerangka kerja seharusnya tidak perlu tahu apa-apa tentang implementasi daripada antarmuka apa yang diekspos kepada mereka, dan banyak rasionalisasi di sini adalah bahwa kami memiliki solusi dan bahwa keakraban mendalam dengan kelas menawarkan banyak cara yang tidak dienkapsulasi dengan baik fungsi mengejek / mematikan.

Tolong perbaiki!

Baru saja menemukan ini jadi lemparkan 2c saya ...

Saya pikir mengejek HttpClient adalah kebodohan.

Meskipun hanya ada satu SendAsync untuk diejek, ada _so_ banyak nuansa untuk HttpResponseMessage , HttpResponseHeaders , HttpContentHeaders , dll, ejekan Anda akan menjadi cepat berantakan dan mungkin berperilaku salah juga.

Aku? Saya menggunakan OwinHttpMessageHandler untuk

  1. Lakukan tes penerimaan HTTP dari luar layanan.
  2. Suntikkan layanan HTTP _Dummy_ ke dalam komponen yang ingin membuat permintaan HTTP keluar ke layanan tersebut. Artinya, komponen saya memiliki parameter ctor opsional untuk HttpMessageHandler .

Saya yakin ada penangan MVC6 yang serupa ....

_Hidup baik-baik saja._

@damianh : Mengapa orang yang menggunakan inti dotnet perlu melakukan apa pun dengan Owin untuk menguji kelas seperti HttpClient? Ini mungkin masuk akal untuk kasus penggunaan tertentu (AspCore dihosting di OWIN) tetapi ini tampaknya tidak terlalu umum.

Ini tampaknya benar-benar mengacaukan kekhawatiran "apa cara paling umum untuk Unit Test HttpClient yang juga konsisten dengan harapan pengguna" dengan "bagaimana cara saya menguji integrasi" :).

Oh _testing_ HttpClient... mengira topiknya sedang menguji hal-hal yang memiliki
ketergantungan pada HttpClient. Jangan pedulikan aku. :)

Pada 5 Des 2016 19:56, "brphelps" [email protected] menulis:

@damianh https://github.com/damianh : Mengapa orang harus menggunakan dotnet core
perlu melakukan apa saja dengan Owin untuk menguji kelas seperti HttpClient? Ini mungkin
masuk akal untuk kasus penggunaan tertentu (AspCore dihosting di OWIN) tetapi
ini tampaknya tidak terlalu umum.

Ini tampaknya benar-benar mengacaukan perhatian "apa yang paling umum
cara Unit Test HttpClient yang juga konsisten dengan harapan pengguna"
dengan "bagaimana cara tes integrasi" :).


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/dotnet/corefx/issues/1624#issuecomment-264942511 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AADgXJxunxBgFGLozOsisCoPqjMoch48ks5rFF5vgaJpZM4EPBHy
.

@damianh - Ini adalah hal-hal yang memiliki ketergantungan, tetapi solusi Anda tampaknya melibatkan banyak ketergantungan lain dalam gambar juga. Saya membayangkan perpustakaan kelas yang kebetulan menggunakan contoh HttpClient yang diteruskan (atau yang DI'ed)-- Di mana Owin muncul?

Bukan karena itu hal menarik yang saya coba tunjukkan (yang
juga akan bekerja pada .net core / netstandard) jadi tolong jangan fokus pada itu.

Yang menarik adalah HttpMessageHandler yang membuat dalam proses
permintaan HTTP dalam memori terhadap titik akhir HTTP dummy.

TestServer Aspnet Core, seperti katana sebelumnya, bekerja dengan cara yang sama
cara.

Jadi kelas Anda yang sedang diuji memiliki HttpClient DI di mana _it_ memiliki
HttpMessageHandler disuntikkan ke dalamnya secara langsung. yang dapat diuji dan
permukaan tegas ada.

Mengejek, bentuk spesifik dari tes ganda, adalah sesuatu yang masih saya sarankan
melawan untuk HttpClient. Dan dengan demikian, tidak perlu untuk IHttpClient.

Pada 5 Des 2016 20:15, "brphelps" [email protected] menulis:

@damianh https://github.com/damianh -- Ini adalah hal-hal yang memiliki
ketergantungan, tetapi solusi Anda tampaknya melibatkan banyak ketergantungan lain
dalam gambar juga. Saya membayangkan perpustakaan kelas yang kebetulan menggunakan
diteruskan dalam contoh HttpClient (atau yang DI'ed)-- Di mana Owin masuk?
gambar?


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/dotnet/corefx/issues/1624#issuecomment-264947538 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AADgXEESfQj9NnKLo8LucFLyajWtQXKBks5rFGK8gaJpZM4EPBHy
.

@damianh -- Benar-benar mengerti apa yang Anda katakan, hal yang menarik adalah -- tetapi orang tidak memerlukan titik akhir HTTP palsu dengan tiruan yang cerdas-- misalnya perpustakaan MockHttp @richardszalay .

Setidaknya tidak untuk pengujian unit :).

Tergantung. Pilihannya bagus :)

Pada 5 Des 2016 20:39, "brphelps" [email protected] menulis:

@damianh https://github.com/damianh -- Benar-benar mengerti apa dirimu
mengatakan hal yang menarik adalah -- tetapi orang tidak membutuhkan HTTP palsu
titik akhir dengan tiruan cerdas-- misalnya @richardszalay
https://github.com/richardszalay's perpustakaan MockHttp.

Setidaknya tidak untuk pengujian unit :).


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/dotnet/corefx/issues/1624#issuecomment-264954210 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AADgXOl4UEDGYCVLpbvwhueaK52VtjH6ks5rFGhlgaJpZM4EPBHy
.

@damianh berkata:
Pilihannya bagus :)

Yap - sangat setuju dengan itu 👍 Yang mana, IMO .. yang tidak kita miliki sekarang **

Saya benar-benar mengerti rasional untuk membuat dan menyuntikkan HMH Anda sendiri. Itulah yang saya lakukan sekarang. _Sayangnya_, saya harus membuat ctor ke-2 (atau menyediakan parameter ctor opsional) _untuk masalah pengujian unit_. Pikirkan saja kalimat itu lagi -> saya membuat OPSI PENGGUNA yang sepenuhnya mungkin ... yang benar-benar hanya untuk mengaktifkan unit test untuk _melakukan hal_. Pengguna akhir/konsumen kelas saya tidak akan pernah _benar-benar_ menggunakan opsi ctor itu ... yang bagi saya berbau.

Kembali ke poin tentang opsi -> saat ini saya tidak merasa kita memilikinya. Kami _memiliki_ untuk _belajar_ tentang internal HttpClient . Buka kap mesin dan lihat di bawah kap mesin. (Saya cukup beruntung mendapatkan TL;DR; dari Sir Fowler The Awesome ™️ yang memangkas waktu penelitian saya).

Jadi - dengan maksud Anda tentang menjadi tanpa antarmuka, bisakah Anda memberikan beberapa contoh kapan antarmuka akan menjadi hal yang buruk? dll. Ingat -> Saya selalu mempromosikan antarmuka sebagai _option_ untuk contoh kecil/sederhana hingga menengah ... tidak 100% dari semua contoh.

Saya tidak mencoba untuk menjebak atau menyerang Anda Damian - hanya mencoba untuk mengerti.

* Ninja Edit: secara teknis, kami sebenarnya memiliki opsi -> kami tidak dapat melakukan apa pun / membuat pembungkus kami sendiri / dll. Maksud saya: tidak banyak opsi yang menyederhanakan banyak hal, *out-of-the-box .

Hai @PureKrome , jangan khawatir hanya berbagi pengalaman.

Istilah 'Uji Unit' telah digunakan beberapa kali dan mari kita pikirkan sejenak. Jadi kami berada di halaman yang sama, saya berasumsi kami memiliki FooClass yang memiliki ketergantungan ctor pada HttpClient (atau HMH ) dan kami ingin _mock_ HttpClient .

Apa yang sebenarnya kami katakan di sini adalah " FooClass dapat membuat permintaan HTTP menggunakan URL apa pun (protokol / host / sumber daya), menggunakan metode apa pun, tipe konten apa pun, enkode transfer apa pun, enkode konten apa pun, _any request headers_, dan dapat menangani tipe konten respons apa pun, kode status apa pun, cookie, pengalihan, pengkodean transfer, header kontrol cache, dll serta batas waktu jaringan, pengecualian yang dibatalkan tugas, dll.".

HttpClient seperti Objek Dewa ; Anda dapat melakukan banyak hal dengannya. HTTP juga merupakan panggilan layanan jarak jauh yang menjangkau di luar proses Anda.

Apakah ini benar-benar pengujian unit ? Jujur sekarang :)

** Ninja Edit juga: Jika Anda ingin membuat unit FooClass dapat diuji di mana ia ingin mendapatkan beberapa json dari server, itu akan memiliki ketergantungan ctor pada Func<CancellationToken, Task<JObject>> atau serupa.

dapatkah Anda memberikan beberapa contoh kapan antarmuka akan menjadi hal yang buruk, tolong

Tidak mengatakan itu! Saya mengatakan IHttpClient tidak akan berguna karena A) itu tidak akan diganti dengan implementasi konkret alternatif dan B) perhatian Objek Dewa seperti yang disebutkan.

Contoh lain? IAssembly .

@damianh -- Status "Kelas Dewa" tidak menjadi perhatian penelepon. Bukan tanggung jawab penelepon untuk memastikan HttpClient adalah / bukan kelas dewa. Mereka tahu bahwa itu adalah objek yang mereka gunakan untuk membuat panggilan HTTP :).

Jadi, dengan asumsi bahwa kita menerima bahwa .NET framework mengekspos kelas Tuhan kepada kita... bagaimana kita bisa mengurangi kekhawatiran kita? Bagaimana cara terbaik untuk merangkumnya? Itu mudah -- Gunakan kerangka kerja ejekan tradisional (Moq adalah contoh yang bagus) dan isolasi sepenuhnya implementasi HttpClient, karena saat orang menggunakannya, kami tidak peduli bagaimana itu dirancang secara internal . Kami hanya peduli tentang bagaimana kami menggunakannya. Masalahnya di sini adalah bahwa pendekatan konvensional tidak bekerja, dan sebagai hasilnya, Anda melihat orang-orang datang dengan solusi yang lebih dan lebih menarik untuk memecahkan masalah :).

Beberapa pengeditan untuk tata bahasa dan kejelasan: D

Sangat setuju @damianh bahwa HttpClient itu seperti Kelas Dewa uber.

Saya juga tidak mengerti mengapa saya perlu tahu tentang semua internal lagi karena bisa ada _begitu banyak purmutasi_ untuk apa yang masuk, apa yang keluar.

Misalnya: Saya ingin mendapatkan stok dari beberapa API. https://api.stock-r-us.com/aapl . Hasilnya adalah beberapa paylod JSON.

jadi, hal-hal yang mungkin saya kacaukan ketika mencoba memanggil titik akhir itu:

  • url titik akhir yang salah?
  • skema yang salah?
  • kehilangan tajuk tertentu?
  • header yang salah
  • payload permintaan salah (mungkin ini adalah POST dengan beberapa badan?)
  • kesalahan jaringan/waktu habis/dll
    ...

Jadi saya mengerti bahwa jika saya ingin menguji hal-hal ini, HMH adalah cara yang bagus untuk melakukan ini karena itu hanya membajak _real_ HMH dan menggantikan apa yang saya katakan, untuk kembali.

Tetapi -- bagaimana jika saya ingin (dengan risiko saya sendiri) mengabaikan semua hal ini dan hanya mengatakan:
_Bayangkan saya ingin menelepon dan endpoint, semuanya baik-baik saja dan saya mendapatkan hasil payload json yang bagus._
Haruskah saya tetap belajar dan khawatir tentang semua hal ini, untuk melakukan sesuatu yang sederhana seperti ini?

Atau ... apakah ini cara berpikir yang buruk. Kita seharusnya tidak pernah berpikir seperti ini karena terlalu hacky? terlalu murah? terlalu rentan terhadap kesalahan?

Sebagian besar waktu saya hanya memanggil titik akhir. Saya tidak memikirkan hal-hal seperti header atau kontrol cache atau kesalahan jaringan. Saya biasanya hanya memikirkan KATA KERJA + URL (dan PAYLOAD, jika diperlukan) dan kemudian HASIL. Saya memiliki kesalahan dalam menangani hal ini untuk Catch-All-The-Things :tm: dan kemudian mengakui kekalahan karena sesuatu yang buruk terjadi :(

Jadi saya salah melihat masalahnya (mis. Saya masih tidak bisa menggoyahkan status n00b saya) -atau- Kelas Dewa ini membuat kami banyak kesulitan menguji unit.

HTTP juga merupakan panggilan layanan jarak jauh yang menjangkau di luar proses Anda.
Apakah ini benar-benar pengujian unit? Jujur sekarang :)

Bagi saya, HttpClient adalah _layanan yang bergantung_. Saya tidak peduli _apa_ layanan yang sebenarnya, saya mengklasifikasikannya sebagai _sesuatu yang melakukan beberapa hal yang berada di luar basis kode saya_. Oleh karena itu, saya tidak ingin itu melakukan hal-hal dengan kekuatan eksternal jadi saya ingin mengontrolnya, membajaknya dan menentukan hasil dari panggilan layanan tersebut. Saya masih mencoba menguji unit kerja _my_. Bagian kecil dari logika saya. Tentu itu mungkin tergantung pada hal-hal lain. Ini masih merupakan pengujian unit jika saya dapat mengontrol seluruh alam semesta, bagian logika ini ada di dalam _dan_ Saya tidak bergantung/integrasi pada layanan lain.

** Suntingan lain: Atau mungkin - jika HttpClient sebenarnya adalah Objek Dewa, mungkin percakapan harus seputar menguranginya menjadi sesuatu ... lebih baik? Atau mungkin, itu sama sekali bukan Objek Dewa? (hanya mencoba membuka perdebatan)..

Sayangnya, saya harus membuat ctor ke-2 (atau memberikan param ctor opsional) untuk masalah pengujian unit. Pikirkan saja kalimat itu lagi -> saya membuat OPSI PENGGUNA yang sepenuhnya mungkin ... yang benar-benar hanya untuk mengaktifkan unit test untuk melakukan hal-hal. Pengguna akhir/konsumen kelas saya tidak akan pernah benar-benar menggunakan opsi ctor itu ... yang bagi saya berbau

Saya tidak yakin apa yang Anda coba katakan di sini:

"Saya tidak suka memiliki ctor kedua yang menerima HttpMessageHandler" ... jadi mintalah tes Anda membungkusnya dalam HttpClient

"Saya ingin menguji unit tanpa membalikkan ketergantungan saya pada HttpClient" ... tangguh?

@richardszalay -- Saya rasa saya mendapatkan sentimen bahwa @PureKrome tidak ingin mengubah kodenya dengan cara yang tidak meningkatkan desain hanya untuk mendukung strategi pengujian yang terlalu keras. Jangan salah paham, saya tidak mengeluh tentang perpustakaan Anda, satu-satunya alasan saya menginginkan solusi yang lebih baik adalah karena saya memiliki harapan yang lebih tinggi dari inti dotnet untuk tidak memerlukannya =).

Saya tidak memikirkan ini dalam hal MockHttp, tetapi menguji/mengejek secara umum.
Ketergantungan terbalik adalah kebutuhan mendasar untuk pengujian unit di .NET, dan itu biasanya melibatkan injeksi ctor. Saya tidak jelas tentang apa alternatif yang diusulkan.

Tentunya jika HttpClient mengimplementasikan IHttpClient, masih akan ada konstruktor yang menerimanya...

Tentunya jika HttpClient mengimplementasikan IHttpClient, masih akan ada konstruktor yang menerimanya...

Sepakat. Saya "melintasi arus" dalam pemikiran saya ketika saya benar-benar baru saja bangun dan berjuang untuk mendapatkan sel tunggal saya di otak saya untuk menjadi hidup.

Abaikan komentar 'ctor' saya dan lanjutkan diskusi, jika ada yang belum pergi dengan bosan

Bisakah kita mengambil langkah mundur ke pertanyaan jika menggunakan HttpClient sebagai ketergantungan benar-benar _dapat_ mengarah ke Pengujian Unit.

Katakanlah saya memiliki kelas FooClass yang menyuntikkan konstruktor ketergantungan. Saya sekarang ingin Unit Test metode pada FooClass , artinya saya menentukan input ke metode saya, saya mengisolasi FooClass sebaik mungkin dari lingkungannya dan saya memeriksa hasilnya terhadap nilai yang diharapkan . Dalam semua pipa ini, saya tidak ingin peduli dengan isi perut objek dependen saya; akan lebih baik bagi saya jika saya bisa menentukan jenis lingkungan tempat FooClass saya harus tinggal: Saya ingin mengejek ketergantungan pada sistem saya yang sedang diuji sedemikian rupa sehingga berperilaku persis seperti yang saya butuhkan untuk agar tes saya berhasil.

Sekarang saat ini saya harus peduli tentang cara HttpClient diimplementasikan karena saya harus menentukan dependensi ( HttpMessageHandler ) untuk dependensi saya yang disuntikkan ( HttpClient ) untuk sistem saya yang sedang diuji. Alih-alih hanya mengejek ketergantungan langsung saya, saya harus membuat implementasi konkretnya dengan ketergantungan berantai yang diejek.

Tema: Saya tidak dapat mengejek HttpClient tanpa mengetahui cara kerja internalnya, dan saya juga harus bergantung pada implementasinya untuk tidak melakukan sesuatu yang mengejutkan karena saya tidak dapat sepenuhnya melewati internal kelas melalui ejekan. Ini pada dasarnya tidak mengikuti Pengujian Unit 101 -- mengeluarkan kode ketergantungan yang tidak terkait dari pengujian Anda =).

Anda dapat melihat tema yang ada di banyak komentar berbeda:

"Bagi saya, HttpClient adalah layanan yang bergantung. Saya tidak peduli apa layanan sebenarnya, saya mengklasifikasikannya sebagai sesuatu yang melakukan beberapa hal yang berada di luar basis kode saya." -- PureKrome

"Sekarang saat ini saya harus peduli tentang cara HttpClient diimplementasikan karena saya harus menentukan ketergantungan (HttpMessageHandler) untuk ketergantungan saya yang disuntikkan (HttpClient) untuk sistem saya yang sedang diuji." -- Thaoden

"Saya akan menanyakan hal yang sama mengingat tingkat abstraksi yang cukup sempurna mengingat ini adalah klien." -- dasjestyr

"HttpMessageHandler secara efektif adalah "implementasi". Anda biasanya membuat HttpClient, meneruskan handler ke konstruktor, dan kemudian membuat lapisan layanan Anda bergantung pada HttpClient." -- richardszalay

" Tetapi masalah utama akan tetap ada - Anda perlu mendefinisikan Handler palsu dan memasukkannya di bawah objek HttpClient." -- Sidharth Nabar

"HttpClient memiliki banyak pilihan untuk unit testability. Itu tidak disegel dan sebagian besar anggotanya adalah virtual. Ia memiliki kelas dasar yang dapat digunakan untuk menyederhanakan abstraksi serta titik ekstensi yang dirancang dengan cermat di HttpMessageHandler" -- ericstj

"Jika API tidak bisa diolok-olok, kita harus memperbaikinya. Tapi itu tidak ada hubungannya dengan antarmuka vs kelas. Antarmuka tidak berbeda dari kelas abstrak murni dari perspektif mockability, untuk semua tujuan praktis." --KrzysztofCwalina

"Dengan hormat, jika opsinya adalah harus menulis Penangan Pesan Palsu Anda sendiri, yang tidak jelas tanpa menggali kodenya, maka itu adalah penghalang yang sangat tinggi yang akan sangat membuat frustasi bagi banyak pengembang." -- ctolkien

Apakah ini benar-benar pengujian unit? Jujur sekarang :)

Saya tidak tahu. Seperti apa ujiannya?

Saya pikir pernyataan itu sedikit berlebihan. Intinya adalah untuk mengejek ketergantungan yang merupakan klien http, sehingga kode dapat berjalan secara terpisah dari dependensi tersebut... dengan kata lain, tidak benar-benar membuat panggilan http. Yang akan kami lakukan adalah memastikan bahwa metode yang diharapkan pada antarmuka dipanggil dalam kondisi yang tepat. Apakah kita seharusnya memilih ketergantungan yang lebih ringan atau tidak tampaknya menjadi diskusi yang sama sekali berbeda.

HttpClient adalah ketergantungan terburuk yang pernah ada. Secara harfiah menyatakan bahwa
dependee dapat "memanggil Internet". Tentu kalian akan mengejek TCP selanjutnya. hehe

Pada 7 Des 2016 5:34 pagi, "Jeremy Stafford" [email protected] menulis:

Apakah ini benar-benar pengujian unit? Jujur sekarang :)

Saya tidak tahu. Seperti apa ujiannya?

Saya pikir pernyataan itu sedikit berlebihan. Intinya adalah untuk mengejek
ketergantungan yang merupakan klien http. Yang akan kami lakukan hanyalah memastikan itu
metode yang diharapkan pada antarmuka dipanggil di bawah kanan
kondisi. Apakah kita seharusnya memilih ketergantungan yang lebih ringan tampaknya
menjadi diskusi yang sama sekali berbeda.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/dotnet/corefx/issues/1624#issuecomment-265353526 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AADgXPL39vnUbWrUuUBtfmbjhk5IBkxHks5rFjdqgaJpZM4EPBHy
.

Ini pada dasarnya tidak mengikuti Pengujian Unit 101 -- mengeluarkan kode ketergantungan yang tidak terkait dari pengujian Anda =).

Secara pribadi, saya tidak berpikir itu interpretasi yang tepat dari praktik khusus itu. Kami tidak mencoba untuk menguji ketergantungan, kami mencoba untuk menguji bagaimana kode kami berinteraksi dengan ketergantungan itu , sehingga tidak terkait. Dan, cukup sederhana, Jika saya tidak dapat menjalankan tes unit terhadap kode saya tanpa menggunakan ketergantungan yang sebenarnya, maka itu tidak menguji secara terpisah. Misalnya, jika pengujian unit itu dijalankan pada server build tanpa akses internet, maka pengujian tersebut kemungkinan akan rusak -- karena itu keinginan untuk mengejeknya. Anda dapat menghapus ketergantungan pada HttpClient, tetapi itu hanya berarti itu akan berakhir di beberapa kelas lain yang Anda tulis yang membuat panggilan -- mungkin saya sedang berbicara tentang kelas itu? Anda tidak tahu karena Anda tidak pernah bertanya. Jadi haruskah layanan domain saya memiliki ketergantungan pada HttpClient? Tidak, tentu saja tidak. Itu akan diabstraksikan ke beberapa jenis layanan abstrak lainnya dan saya malah mengejeknya. Oke, jadi di lapisan yang menyuntikkan abstraksi apa pun yang saya buat -- di suatu tempat di rantai, _something_ akan tahu tentang HttpClient dan sesuatu akan dibungkus oleh sesuatu yang lain yang saya tulis, dan sesuatu yang lain adalah sesuatu yang saya 'm masih akan menulis unit test untuk.

Jadi, jika pengujian unit saya untuk beberapa jenis mediator sedang menguji bahwa panggilan yang tidak berhasil ke layanan di internet menghasilkan kode saya memutar kembali beberapa bentuk transaksi vs. panggilan yang berhasil ke layanan yang menghasilkan komitmen itu transaksi, maka saya harus memiliki kendali atas hasil panggilan itu untuk mengatur kondisi pengujian itu sehingga saya dapat menyatakan bahwa kedua skenario berperilaku dengan benar.

Saya dapat memahami pendapat beberapa orang bahwa HttpClient bukan tingkat abstraksi yang benar, namun saya rasa tidak ada yang bertanya tentang "tingkat" kelas konsumsi, yang agak bersifat dugaan. Seseorang dapat mengabstraksikan panggilan HTTP ke layanan dan antarmuka itu, tetapi beberapa dari kita masih ingin menguji bahwa "pembungkus" kita untuk panggilan HTTP itu berperilaku seperti yang diharapkan, yang berarti bahwa kita memerlukan kontrol atas responsnya. Saat ini, ini dapat dicapai dengan memberikan HttpClient penangan palsu yang sedang saya lakukan sekarang, dan tidak apa-apa, tetapi intinya adalah jika Anda menginginkan cakupan itu, Anda harus menggunakan HttpClient di beberapa titik untuk setidaknya memalsukan tanggapan.

Tapi, sekarang biarkan aku mundur. Semakin saya melihatnya, itu terlalu gemuk untuk antarmuka yang dapat diolok-olok (yang telah dikatakan). Saya pikir itu mungkin hal mental yang berasal dari nama "Klien". Saya tidak mengatakan bahwa saya pikir itu salah, melainkan, itu sedikit tidak selaras dengan implementasi "Klien" lain yang mungkin dilihat orang di luar sana di alam liar akhir-akhir ini. Kami melihat banyak "klien" di luar sana hari ini yang benar-benar fasad layanan, jadi ketika seseorang melihat nama KLIEN Http, mereka mungkin memikirkan hal yang sama dan mengapa Anda tidak mengejek layanan, bukan?

Sekarang saya melihat bahwa handler adalah bagian yang penting bagi kode, jadi saya akan merancangnya di masa mendatang.

Pada dasarnya, saya pikir mungkin keinginan kami untuk mengejek HttpClient dengan cara yang terkait dengan kecenderungan pengembang rata-rata untuk membuat contoh baru HttpClient alih-alih menggunakannya kembali yang merupakan praktik yang buruk. Dengan kata lain, kami meremehkan apa itu HttpClient dan seharusnya fokus pada handler. Juga, pawangnya abstrak, sehingga dapat dengan mudah diejek.

Oke, saya dijual. Saya tidak lagi ingin antarmuka di HttpClient untuk diejek.

Setelah berjuang lama, saya membuat Antarmuka IHttpHandler saya sendiri dan mengimplementasikan semuanya untuk kasus penggunaan saya. Membuat kode konkret saya lebih mudah dibaca dan tes dapat diejek tanpa beberapa hal yang membosankan.

Hampir tidak mungkin untuk membagikan instance HttpClient dalam aplikasi apa pun segera setelah Anda perlu mengirim header HTTP yang berbeda pada setiap permintaan (yang penting ketika berkomunikasi dengan layanan web RESTful yang dirancang dengan benar). Saat ini HttpRequestHeaders DefaultRequestHeaders terikat ke instance dan tidak memanggilnya secara efektif menjadikannya stateful. Sebaliknya {Method}Async() harus menerimanya apa yang akan membuat HttpClient stateless dan benar-benar dapat digunakan kembali.

@abatishchev Tetapi Anda dapat menentukan header pada setiap HttpRequestMessage.

@richardszalay Saya tidak mengatakan itu sepenuhnya tidak mungkin, saya mengatakan bahwa HttpClient tidak dirancang dengan baik untuk tujuan ini. Tak satu pun dari {Method}Async() menerima HttpRequestMethod , hanya SendAsync() yang menerima. Tapi apa tujuan dari sisanya?

Tapi apa tujuan dari sisanya?

Memenuhi 99% kebutuhan.

Apakah ini berarti mengatur tajuk adalah 1% dari kasus penggunaan? Saya ragu.

Either way ini tidak akan menjadi masalah jika metode tersebut memiliki kelebihan menerima HttpResponseMessage.

@abatishchev Saya tidak meragukannya, tetapi bagaimanapun juga, saya akan menulis metode ekstensi jika saya menemukan diri saya dalam skenario Anda.

Saya bermain-main dengan gagasan untuk mungkin menghubungkan HttpMessageHandler dan mungkin HttpRequestMessage karena saya tidak suka harus menulis palsu (vs mengejek). Tetapi semakin jauh ke dalam lubang kelinci itu, semakin Anda menyadari bahwa Anda akan mencoba memalsukan objek nilai data aktual (misalnya HttpContent) yang merupakan latihan yang sia-sia. Jadi saya pikir mendesain kelas dependen Anda untuk secara opsional mengambil HttpMessageHandler sebagai argumen ctor dan menggunakan yang palsu untuk pengujian unit adalah rute yang paling tepat. Saya bahkan berpendapat bahwa membungkus HttpClient juga membuang-buang waktu ...

Ini akan memungkinkan Anda untuk menguji unit kelas dependen Anda tanpa benar-benar melakukan panggilan ke internet, yang Anda inginkan. Dan palsu Anda dapat mengembalikan kode status dan konten yang telah ditentukan sebelumnya sehingga Anda dapat menguji bahwa kode dependen Anda memprosesnya seperti yang diharapkan, yang sekali lagi, apa yang sebenarnya Anda inginkan.

@dasjestyr Apakah Anda mencoba membuat antarmuka untuk HttpClient (yang seperti membuat pembungkus untuk itu) alih-alih antarmuka untuk HttpMessageHandler atau HttpRequestMessage ..

/aku penasaran.

@PureKrome Saya membuat sketsa membuat antarmuka untuk itu dan di situlah saya segera menyadari bahwa itu tidak ada gunanya. HttpClient benar-benar hanya mengabstraksikan banyak hal yang tidak penting dalam konteks pengujian unit, dan kemudian memanggil penangan pesan (yang merupakan poin yang dibuat beberapa kali di utas ini). Saya juga mencoba membuat pembungkus untuk itu, dan itu sama sekali tidak sepadan dengan pekerjaan yang diperlukan untuk mengimplementasikannya atau menyebarkan praktiknya (yaitu "yo, semua orang melakukan ini alih-alih menggunakan HttpClient secara langsung"). Jauh lebih mudah untuk hanya fokus pada handler, karena memberikan semua yang Anda butuhkan dan secara harfiah terdiri dari satu metode.

Yang mengatakan, saya telah membuat RestClient saya sendiri, tetapi itu memecahkan masalah berbeda yang menyediakan pembuat permintaan yang lancar, tetapi bahkan klien itu menerima penangan pesan yang dapat digunakan untuk pengujian unit atau untuk mengimplementasikan penangan khusus yang menangani hal-hal seperti rantai penangan yang memecahkan masalah lintas sektoral (misalnya logging, auth, retry logic, dll.) yang merupakan jalan yang harus ditempuh. Itu tidak spesifik untuk klien istirahat saya, itu hanya kasus penggunaan yang bagus untuk mengatur pawang. Saya sebenarnya menyukai antarmuka HttpClient di namespace Windows karena alasan ini, tetapi saya ngelantur.

Saya pikir itu masih berguna untuk menghubungkan pawang, tetapi itu harus berhenti di situ. Kerangka kerja mengejek Anda kemudian dapat diatur untuk mengembalikan contoh HttpResponseMessage yang telah ditentukan sebelumnya.

Menarik. Saya telah menemukan (bias pribadi?) perpustakaan pembantu saya berfungsi dengan baik saat menggunakan penangan pesan konkret (palsu) .. vs. beberapa hal antarmuka pada level rendah itu.

Saya masih lebih suka untuk tidak harus menulis perpustakaan itu atau menggunakannya :)

Saya tidak melihat ada masalah dengan membuat perpustakaan kecil untuk membuat palsu. Saya mungkin melakukannya ketika saya bosan dengan tidak ada lagi yang bisa dilakukan. Semua barang http saya sudah diabstraksi dan diuji jadi saya tidak benar-benar menggunakannya saat ini. Saya hanya tidak melihat nilai apa pun dalam membungkus HttpClient untuk tujuan pengujian unit. Memalsukan pawang adalah semua yang Anda butuhkan. Memperluas fungsionalitas adalah topik yang sepenuhnya terpisah.

Ketika sebagian besar basis kode diuji menggunakan antarmuka tiruan, akan lebih mudah dan konsisten jika basis kode lainnya diuji dengan cara yang sama. Jadi saya ingin melihat antarmuka IHttpClient. Seperti IFileSystemOperarions dari ADL SDK atau IDataFactoryManagementClient dari ADF management SDK, dll.

Saya masih berpikir Anda kehilangan intinya, yaitu HttpClient tidak perlu diejek, hanya pawangnya. Masalah sebenarnya adalah cara orang melihat HttpClient. Ini bukan kelas acak yang harus diperbarui setiap kali Anda berpikir untuk menelepon internet. Faktanya, ini adalah praktik terbaik untuk menggunakan kembali klien di seluruh aplikasi Anda -- itu masalah besar. Pisahkan kedua hal itu, dan itu lebih masuk akal.

Plus, kelas dependen Anda seharusnya tidak peduli dengan HttpClient, hanya data yang dikembalikan olehnya -- yang berasal dari handler. Pikirkan seperti ini: apakah Anda akan mengganti implementasi HttpClient dengan yang lain? Mungkin... tapi tidak mungkin. Anda tidak perlu mengubah cara kerja klien, jadi mengapa repot-repot mengabstraksikannya? Penangan pesan adalah variabel. Anda ingin mengubah cara respons ditangani, tetapi bukan apa yang dilakukan klien. Bahkan pipeline di WebAPI difokuskan pada handler (lihat: mendelegasikan handler). Semakin saya mengatakannya, semakin saya mulai berpikir bahwa .Net harus membuat klien statis dan mengelolanya untuk Anda... tapi maksud saya... terserah.

Ingat untuk apa antarmuka itu. Mereka bukan untuk pengujian -- itu hanya cara cerdas untuk memanfaatkannya. Membuat antarmuka semata-mata untuk tujuan itu adalah konyol. Microsoft memberi Anda apa yang Anda butuhkan untuk memisahkan perilaku penanganan pesan, dan itu berfungsi sempurna untuk pengujian. Sebenarnya, HttpMesageHandler adalah abstrak, jadi saya pikir sebagian besar kerangka kerja mengejek seperti Moq masih akan bekerja dengannya.

Heh @dasjestyr - Saya juga berpikir Anda mungkin melewatkan poin utama diskusi saya.

Fakta bahwa kami (pengembang) perlu _belajar_ banyak tentang Penangan Pesan, dll.. untuk memalsukan respons adalah poin _main_ saya tentang semua ini. Tidak begitu banyak tentang antarmuka. Tentu, saya (secara pribadi) lebih suka antarmuka daripada pembungkus virtuals r sehubungan dengan pengujian (dan karenanya mengejek) ...

Saya berharap inti utama dari utas epik ini adalah untuk menyoroti bahwa .. ketika menggunakan HttpClient dalam suatu aplikasi, itu adalah PITA untuk mengujinya.

Status quo "pergi pelajari pipa HttpClient, yang akan membawa Anda ke HttpMessageHandler, dll" adalah situasi yang buruk. Kami tidak perlu melakukan ini untuk banyak perpustakaan lain, dll.

Jadi saya berharap ada yang bisa dilakukan untuk meringankan PITA ini.

Ya, PITA adalah opini - saya akan mengakuinya sepenuhnya. Beberapa orang tidak berpikir itu PITA sama sekali, dll.

Masalah sebenarnya adalah cara orang melihat HttpClient. Ini bukan kelas acak yang harus diperbarui setiap kali Anda berpikir untuk menelepon internet.

Sepakat! Tetapi sampai baru-baru ini, ini tidak diketahui dengan baik - yang mungkin menyebabkan kurangnya dokumentasi atau pengajaran atau sesuatu.

Plus, kelas dependen Anda seharusnya tidak peduli dengan HttpClient, hanya data yang dikembalikan olehnya

Ya - setuju.

yang berasal dari pawang.

sebuah Apa? Hah? Oh -- sekarang Anda meminta saya untuk membuka kap mesin dan belajar tentang pipa ledeng? ... Lihat di atas lagi dan lagi.

Pikirkan seperti ini: apakah Anda akan mengganti implementasi HttpClient dengan yang lain?

Tidak.

.. dll ..

Sekarang, saya tidak bermaksud troll, dll. Jadi tolong jangan berpikir bahwa saya mencoba menjadi brengsek dengan selalu membalas dan mengulangi hal yang sama berulang-ulang.

Saya telah menggunakan perpustakaan pembantu saya selama berabad-abad sekarang yang hanya merupakan cara yang dimuliakan menggunakan penangan pesan khusus. Besar! Ini bekerja dan bekerja dengan baik. Saya hanya berpikir bahwa ini bisa _all_ diekspos sedikit lebih bagus ... dan itulah yang saya harap utas ini benar-benar cocok.

EDIT: pemformatan.

(Saya baru saja melihat kesalahan tata bahasa dalam judul edisi ini dan sekarang saya tidak dapat menghapusnya)

Dan itu adalah permainan panjang _real_ saya :)

QED

(sebenarnya - sangat memalukan 😊)

EDIT: sebagian dari saya tidak ingin mengeditnya .. untuk nostalgia ....

(Saya baru saja melihat kesalahan tata bahasa dalam judul edisi ini dan sekarang saya tidak dapat menghapusnya)

Aku tahu! Sama!

Fakta bahwa kami (pengembang) perlu belajar banyak tentang Penangan Pesan, dll. untuk memalsukan respons adalah poin utama saya tentang semua ini. Tidak begitu banyak tentang antarmuka. Tentu, saya (secara pribadi) lebih suka antarmuka daripada pembungkus virtuals r sehubungan dengan pengujian (dan karenanya mengejek) ...

apa?

Pernahkah Anda melihat HttpMessageHandler? Ini adalah kelas abstrak yang secara harfiah merupakan metode tunggal yang mengambil HttpRequestMessage dan mengembalikan HttpResponseMessage -- dibuat untuk mencegat perilaku yang terpisah dari semua sampah transportasi tingkat rendah yang persis seperti yang Anda inginkan dalam pengujian unit. Untuk memalsukannya, terapkan saja. Pesan yang masuk adalah yang Anda kirim HttpClient, dan respon yang keluar terserah Anda. Misalnya, jika saya ingin tahu bahwa kode saya menangani badan json dengan benar, maka mintalah respons untuk mengembalikan badan json yang Anda harapkan. Jika Anda ingin melihat apakah ia menangani 404 dengan benar, maka minta ia mengembalikan 404. Tidak ada yang lebih mendasar dari itu. Untuk menggunakan handler, kirimkan saja sebagai argumen ctor untuk HttpClient. Anda tidak perlu mencabut kabel apa pun atau mempelajari cara kerja internal apa pun.

Saya berharap inti utama dari utas epik ini adalah untuk menyoroti bahwa .. ketika menggunakan HttpClient dalam suatu aplikasi, itu adalah PITA untuk mengujinya.

Dan inti utama dari apa yang telah ditunjukkan banyak orang adalah bahwa Anda melakukan kesalahan (itulah sebabnya ini adalah PITA, dan saya mengatakan ini secara langsung tetapi dengan hormat) dan menuntut agar HttpClient dihubungkan untuk tujuan pengujian sama dengan membuat fitur untuk mengkompensasi bug alih-alih mengatasi akar masalah yang, dalam hal ini, Anda beroperasi pada level yang salah.

Saya pikir HttpClient di namespace windows sebenarnya memisahkan konsep handler menjadi filter , tetapi ia melakukan hal yang sama persis. Saya pikir handler/filter dalam implementasi itu sebenarnya dihubungkan dengan yang agak saya sarankan sebelumnya.

apa?
Pernahkah Anda melihat HttpMessageHandler?

Awalnya tidak, karena ini:

var payloadData = await _httpClient.GetStringAsync("https://......");

artinya, metode yang diekspos pada HttpClient sangat bagus dan membuat segalanya _sangat_ mudah :) Yay! Cepat, sederhana, berhasil, MENANG!

Ok - jadi sekarang mari kita gunakan dalam pengujian ... dan sekarang kita perlu meluangkan waktu untuk mempelajari apa yang terjadi di bawahnya .. yang membawa kita untuk belajar tentang HttpMessageHandler dll.

Sekali lagi saya terus mengatakan ini => pembelajaran ekstra tentang pipa ledeng HttpClient (yaitu HMH , dll) adalah PITA. Setelah Anda _menyelesaikan pembelajaran itu_ .. ya, saya kemudian tahu cara menggunakannya dll ... meskipun saya juga tidak suka terus menggunakannya tetapi perlu, untuk mengejek panggilan jarak jauh ke titik akhir pihak ke-3. Tentu, itu berpendirian. Kami hanya bisa setuju untuk tidak setuju. Jadi tolong - saya tahu tentang HMH dan bagaimana menggunakannya.

Dan inti utama dari apa yang telah ditunjukkan banyak orang adalah bahwa Anda salah melakukannya

Yap - orang tidak setuju dengan saya. Tidak masalah. Juga - orang-orang juga setuju dengan saya. Sekali lagi, tidak ada masalah.

dan _menuntut_ agar HttpClient dihubungkan untuk tujuan pengujian serupa dengan membuat fitur untuk mengkompensasi bug alih-alih mengatasi akar masalah yang, dalam hal ini, Anda beroperasi pada level yang salah.

Oke - saya dengan hormat tidak setuju dengan Anda di sini (tidak apa-apa). Sekali lagi, berbeda pendapat.

Tapi srly. Utas ini telah berlangsung begitu lama. Aku sudah benar-benar pindah sekarang. Saya mengatakan bagian saya, beberapa orang mengatakan YA! ada yang bilang TIDAK! Tidak ada yang berubah dan saya baru saja ... menerima status quo dan berguling dengan segalanya.

Saya minta maaf Anda hanya tidak menerima jalan pikiran saya. Saya bukan orang jahat dan berusaha untuk tidak terdengar kasar atau jahat. Ini hanya saya yang mengungkapkan bagaimana perasaan saya saat berkembang dan bagaimana saya percaya orang lain juga merasa. Itu saja.

Saya sama sekali tidak tersinggung. Saya awalnya melompat ke utas ini dengan pendapat yang sama bahwa klien harus dapat diejek, tetapi kemudian beberapa poin yang sangat bagus dibuat tentang apa itu HttpClient jadi saya duduk dan benar-benar memikirkannya, dan kemudian mencoba menantangnya sendiri dan akhirnya saya sampai pada kesimpulan yang sama yaitu bahwa HttpClient adalah hal yang salah untuk diejek dan mencoba melakukannya adalah latihan yang sia-sia yang menyebabkan jauh lebih banyak pekerjaan daripada nilainya. Menerima itu membuat hidup lebih mudah. Jadi saya juga sudah move on. Saya hanya berharap bahwa orang lain pada akhirnya akan memberi diri mereka istirahat dan menerima apa adanya.

Sebagai tambahan:

Awalnya tidak, karena ini:

var payloadData = menunggu _httpClient.GetStringAsync("https://......");

artinya, metode yang diekspos di HttpClient sangat bagus dan membuat segalanya sangat mudah :) Yay! Cepat, >sederhana, berhasil, MENANG!

Saya berpendapat bahwa dalam hal SOLID, antarmuka ini tetap tidak pantas. Klien IMO, permintaan, tanggapan adalah 3 tanggung jawab yang berbeda. Saya dapat menghargai metode kenyamanan pada klien, tetapi mempromosikan kopling ketat dengan menggabungkan permintaan dan tanggapan dengan klien, tapi itu preferensi pribadi. Saya memiliki beberapa ekstensi ke HttpResponseMessage yang melakukan hal yang sama tetapi tetap bertanggung jawab untuk membaca respons dengan pesan respons. Dalam pengalaman saya, dengan proyek besar, "Sederhana" tidak pernah "Sederhana" dan hampir selalu berakhir dengan BBOM. Namun ini adalah diskusi yang sama sekali berbeda :)

Sekarang kami memiliki repo baru khusus untuk diskusi desain, silakan lanjutkan diskusi di https://github.com/dotnet/designs/issues/9 atau buka edisi baru di https://github.com/dotnet/designs

Terima kasih.

Mungkin bisa dipertimbangkan solusi di bawah ini hanya untuk tujuan pengujian:

kelas publik GeoLocationServiceForTest : GeoLocationService, IGeoLocationService
{
publik GeoLocationServiceForTest (sesuatu: sesuatu, HttpClient httpClient): basis (sesuatu)
{
base._httpClient = httpClient;
}
}

Saya akhirnya menggunakan MockHttp @richardszalay .
Terima kasih, Richard, Anda menyelamatkan hari saya!

Oke, saya bisa hidup dengan HttpClient tanpa antarmuka. Tetapi dipaksa untuk mengimplementasikan kelas yang mewarisi HttpMessageHandler karena SendAsync dilindungi sungguh menyebalkan.

Saya menggunakan NSubstitute, dan ini tidak berhasil:

var httpMessageHandler = 
    Substitute.For<HttpMessageHandler>(); // cannot be mocked, not virtual nor interfaced
httpMessageHandler.SendAsync(message, cancellationToken)
    .Return(whatever); // doesn't compile, it's protected
var httpClient = new HttpClient(httpMessageHandler)

Benar-benar mengecewakan.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

yahorsi picture yahorsi  ·  3Komentar

Timovzl picture Timovzl  ·  3Komentar

bencz picture bencz  ·  3Komentar

aggieben picture aggieben  ·  3Komentar

btecu picture btecu  ·  3Komentar