Pengujian sebagai prinsip universal
Kami telah merayakan milenium selama hampir seperempat abad, dan pengujian baru saja memasuki hidup kami ... Sulit meyakinkan pengembang pemula untuk menggunakan teknik luar biasa ini dalam pekerjaan mereka ... Apa yang bisa kami katakan tentang pengembang, hanya manusia, dan tidak selalu mungkin untuk memahami bahwa pengujian adalah dasar sistem berkelanjutan! Betapa sulitnya meyakinkan seorang pramuniaga bahwa menguji suatu produk baru tidak berarti memakannya! Bahkan penjaga keamanan berpengalaman jelas bekerja dengan cara lama - mereka mencoba untuk mengejar ketinggalan dan memilih tes. Dan Anda tidak akan membuktikan kepada mereka bahwa jika Tuhan Allah sendiri tidak merendahkan untuk menggunakan TDD dalam karyanya (ingat Banjir Besar), maka, seperti yang mereka katakan, Tuhan sendiri memerintahkan ...
Jumlah perceraian meningkat - mengapa? Ya sama saja! TDD! Tes dulu - lalu menikah! Tidak, laki-laki kecil yang mudah tertipu dalam mantel kulit domba yang longgar, ingin iklan yang mengeksploitasi seks, meluncurkan istri muda mereka ke dalam produksi ...
Nah, kami bersama Anda dari tes lain, pengujian pertama - lalu yang lainnya!
Saya mengenali tester dengan kiprah ...
Jadi, ketika saya mulai menulis basis data kode berikutnya, saya memikirkannya, mengapa tidak melakukan pengujian otomatis pada lapisan DAL saya langsung pada tes yang dibangun ke dalam VisualStudio?
Dan saya berhasil! Transparan ke EntityFramework, tanpa sulap di bawah selimut dan penipuan dengan benda palsu. Siapa yang peduli - mengungkap VS, berpakaian seperti penguji dan pergi! (Saya selalu berpakaian seperti tester)
Berbohong kepada semua orang, ini sedang menguji!
Kasus kehidupan:Bekerja pada proyek dengan kode berikut:
ObjectLink link = this.ObjectLinks.ToList().Where(x => x.SpotCode.ToLowerInvariant() == code.ToLowerInvariant()).SingleOrDefault();
Kode ini tidak dicakup oleh tes, karena tidak punya waktu - sangat mendesak untuk meluncurkan fungsionalitas baru yang terkait dengan pemasaran. Semuanya bekerja ketika memeriksa secara manual, dan saya sudah santai ... tapi Bill Gates merayap tanpa disadari ...
Musim gugur yang puitis di St. Petersburg, salju dan hujan mengguyur wajahku dengan lembut, tanah mengalir deras dari kaki celana dan gadis-gadis yang tidak dikenal tersenyum melalui rias wajah, menuangkan truk yang lewat ... Aku sudah menggunakan jari untuk mencungkil hidung, ketika itu berbahaya, tanpa menyatakan perang, sebelum saat fajar, Microsoft memotong kawat berduri dan merilis pembaruan inti 3.0. Hoster telah diperbarui, saya juga diperbarui, mengapa masuk yang lama - apakah saya lebih buruk daripada hoster? Saya memeriksa semuanya seperti itu dan meluncurkan pembaruan ... dan kemudian saya meluncurkan mata saya! Fungsionalitas baru tidak berfungsi! sepertinya saya mengujinya sebelumnya - apa yang bisa terjadi?
Dan ini terjadi: Billy tua memutuskan untuk menghentikannya dari LINQ ToLowerInvariant ... sekarang Anda harus memanggilnya terlebih dahulu dan memasukkan nilai jadi ... jika kode tersebut ditutupi dengan tes, saya akan segera melihatnya saat pengujian. Sangat baik bahwa saya memperhatikan semuanya sendiri, saya tidak harus bersumpah pada pelanggan, karena penguji malu untuk memerah ... saya harus menyelesaikan masalah dan membuat penyebaran baru.
perangkat dan bahan:
Microsoft VisualStudio 2019
asp.net Core 3.1 (Saya menginstalnya dengan studio, jika ada yang bisa dikirim melalui instal menu proyek frameworks lainnya)
SQL Server Express (dilengkapi dengan studio)
Ekstensi Git ke studio visual (termasuk)
Biasanya, dalam tes unit, setiap tes harus diisolasi, dan keadaan di antara mereka tidak bertahan. Jadi kita akan benar-benar mendapatkan tes integrasi, tetapi kita akan menggunakan MSTest untuk ini. Saya berharap mereka tidak membawa kami ke polisi untuk ini.
Dalam beberapa edisi saya bertemu dengan penggunaan benda-benda tiruan untuk menguji database.
Pada pandangan pertama, idenya bagus sampai interaksi kompleks antara tabel dimulai.
Maka pengaturan tiruan akan membutuhkan lebih dari pengujian itu sendiri. Tetapi sebenarnya itu - Control + C - Control + V! kita semua adalah batasan basis data yang telah terdaftar di EF, basis data, DataAnnotations atau duplikat FluentAPI di lapisan tiruan. Dan menyalin itu seperti melanggar pola ... ayah, warga negara, melanggar ... tidak baik!
Dan jika konfigurasi tiruannya rumit, dan misalnya kita membuat kesalahan dalam pembatasan di sana, ternyata - tes tiruan akan lulus, tetapi apakah akan ada kesalahan pada basis nyata?
Itu semua membuat saya tertarik, dan saya memutuskan untuk menguji pendekatan baru.
Idenya datang, seperti biasa, dari TRIZ:
sistem ideal adalah yang tidak ada, tetapi fungsinya dijalankan . Dan saya pikir saya perlu menggunakan database itu sendiri dalam tes.
Dan itu agak berhasil bagi saya. Saya ingin membagikan ini, saya harap seseorang membantu.
Cons of mock:
- banyak preset yang menguji
- tes menjadi kotor, banyak kode tambahan
- sulit untuk menguji migrasi
- berperilaku baik hanya di bawah pengawasan, secara aktual dapat menghasilkan kesalahan yang tidak diketahui
- ketika mengubah struktur database Anda harus terus-menerus memanjat ke mock dan mengubah semuanya di sana juga
Kelebihan pengujian pada basis nyata:
- program berperilaku persis seperti pada server tempur
- tes lebih sederhana, Anda dapat membangun mereka satu demi satu dengan cara yang sama data diisi dalam database
- kami sendiri dapat menyesuaikan kebersihan basis data dengan memberi nomor pada tes (dalam MSTest, pengujian dilakukan berdasarkan abjad)
- Anda dapat melihat waktu di mana tes dilakukan (pada server sebenarnya akan berbeda, tetapi setidaknya urutannya terlihat - 10 kali lebih lama, 2 kali, dan Anda sudah dapat mengevaluasi cara kerja program, efisien atau tidak)
- dapat menguji prosedur yang tersimpan
Ada beberapa kesulitan dalam pendekatan ini, yang telah saya gali selama beberapa hari, tetapi kami akan berhasil menyelesaikannya, dan semoga backend batu saya bersama Anda!
Ayo pergi!
Kami membuat proyek baru Aplikasi Web ASP.Net Core 3.1 (Model-View-Controller), mengubah Otentikasi ke Akun Pengguna Individual (Menyimpan akun pengguna di dalam aplikasi) dan klik buat

Mulai sekarang, saya akan menyimpan snapshot proyek ke git, Anda dapat mengunduhnya dan mengunggah setiap cabang untuk bereksperimen dengannya
github.com/3263927/Habr_1Snapshot: Snapshot_0_ProjectCreatedTentang repositoriBahkan ketika saya bekerja sendiri, saya selalu menggunakan repositori - sekarang telah menjadi sangat nyaman, dibangun langsung ke dalam Visual Studio, tanpa baris perintah, semuanya bekerja dengan baik langsung dari VS. Anda dapat bereksperimen dan mengubah apa pun yang Anda inginkan, maka Anda selalu dapat memperbaiki rollback komit atau beralih ke cabang lama. Menghemat banyak waktu dan tenaga, saya menyarankan semua orang. Dan terintegrasi dengan github secara gratis. Benar, ada beberapa pria di sana beberapa tahun yang lalu yang menghapus semuanya ... Jadi untuk berjaga-jaga, saya meletakkan semua proyek di Dropbox dan memperbarui seminggu sekali, serta mengarsipkan semua proyek dan mengunggah versi terbaru secara manual ke Google Drive. Nah, di telepon SD ada 120 pertunjukan, di sana juga, sebagai cadangan, tiba-tiba, tiba-tiba ... Beberapa flash drive dengan salinan di saku mereka begitu tak terlihat!
Pada titik ini saya membuat repositori, jadi sekarang saya harus merencanakan pekerjaan untuk membuat cabang baru. Di masa depan, menggunakan kata kunci Snapshot, akan dimungkinkan untuk menemukan poin pemulihan jika terjadi kesalahan.
Saya akan membuat cabang baru di repositori langsung dari VisualStudio, dan saya akan menyebutnya "Sister of Talent" untuk singkat (lelucon, Snap_1_DataBases).
tujuan: untuk membuat koneksi dan basis yang berfungsi .
Kami mulai membuat pangkalan kami.
Saya harus mengatakan segera bahwa kami akan memiliki 3 database - satu tes (pada mesin lokal), produksi lain (pada server jarak jauh, bekerja) dan kerja lokal lainnya (untuk memeriksa kesehatan situs dalam konfigurasi DEBUG).
Logikanya adalah ini:
- jika kita ingin menjalankan situs di mesin lokal dan melihat cara kerjanya, maka Habr1_Local akan bekerja untuk kita
- jika kita memasukkan kode dalam produksi maka Habr1_Production akan bekerja
- ketika infrastruktur pengujian kami mulai menguji, ia harus menemukan basis Habr1_Test dan menjalankannya
Namun, kami memiliki satu kontradiksi - hanya ada dua konfigurasi, Debug dan Release. Ini masih merupakan masalah, tetapi kemudian akan diselesaikan.
Jadi, kami membuat program yang berfungsi minimal - sebagai permulaan kami hanya memeriksa apakah setidaknya satu database berfungsi untuk kami. Mari kita ciptakan ... dengan tangan Visual Studio itu sendiri!
Buka file appsettings.json
Ada beberapa baris:
"ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApp-[- , ];Trusted_Connection=True;MultipleActiveResultSets=true" },
Di sana Anda harus memasukkan nama string koneksi, nama server, dan nama database yang benar. Saya harus mengatakan segera bahwa koneksi lain digunakan dalam produksi, tetapi sekarang kita tidak membutuhkan ini. Tugas kami adalah membuat dua basis data (lokal dan uji, produksi hanyalah sebuah contoh - kami akan menggunakannya dalam konfigurasi rilis. Kemudian dapat diganti dengan basis data yang berfungsi).
Mengapa ini dibutuhkan?
Konfigurasi Visual Studio memungkinkan Anda mengubah beberapa pengaturan dengan mengalihkan konfigurasi di panel visual studio:

Secara umum, lingkungan pengembangan hanya menyatakan konstanta DEBUG, yang kemudian dapat dibaca dari mana saja dalam kode dan memahami konfigurasi apa yang saat ini kita gunakan, yang mana yang aktif.
Remote debugger tidak selalu tersedia, misalnya, pada hosting colocation. Kami dapat menjalankan server asp.net kami secara lokal, dan terhubung ke database ke remote, dan kami dapat melihat semua kesalahan yang ada di produksi. Di sini, di konfigurasi rilis kami akan melakukan ini, dan konfigurasi debug akan bekerja dengan database lokal kami. Dan kami tidak akan melakukan konfigurasi pengujian, untuk alasan keamanan - agar tidak secara tidak sengaja menghapus data apa pun, lupa untuk mengganti konfigurasi
Jadi, kami mulai mengubah string koneksi.
Nama server - Anda dapat melihatnya pada tampilan tab -> SQL server object explorer

(Saya akan menghapus nama komputer saya dengan hati-hati, jika tidak, Anda akan menghitung saya dengan IP dan mengetik sesuatu).
Jadi saya punya ini (localdb) \ ProjectsV13. Saya tidak tahu mengapa, selama instalasi saya memanggil SQL saya.
Ini berarti bahwa string koneksi kita menjadi
"DefaultConnection": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local; Trusted_Connection=True;MultipleActiveResultSets=true"
Anda mungkin memilikinya secara berbeda, tetapi hanya ProjectV13. Sisanya harus dibiarkan begitu saja.
Ubah DefaultConnection ke Habr1_Local
Ternyata seperti ini:
"ConnectionStrings": { "Habr1_Local": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local;Trusted_Connection=True;MultipleActiveResultSets=true" },
Sekarang Anda harus pergi ke file Startup.cs dan ganti DefaultConnection dengan Habr1_Local di sana:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
berubah menjadi
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Habr1_Local")));
Kami memulai proyek kami dalam konfigurasi Debug, browser terbuka, kami melihat halaman pertama, klik tombol Login, masukkan email yang valid di sana (tidak akan mengirimi Anda surat, itu hanya memvalidasi format) dan klik Login - dan kami melihat layar ini:

Klik Terapkan Migrasi, tunggu hingga konfirmasi muncul di sebelah kanan tombol biru bahwa migrasi telah berlalu dan Anda perlu menyegarkan halaman, menyegarkan, klik konfirmasi untuk mengirim ulang data dan mendapatkan layar yang mengatakan Login Gagal:

Jika layar seperti itu terlihat, maka semuanya baik-baik saja - setelah permintaan berlalu dan mengembalikan upaya login yang tidak valid, maka tidak ada kesalahan database, tetapi itu sama sekali tidak menemukan pengguna seperti itu.
Sedikit tentang migrasi:Ini adalah alat yang kompleks dan sulit untuk menyentuhnya di artikel ini, tetapi Anda dapat menyentuh sedikit.
Misalnya, Anda perlu mengubah status database menjadi yang spesifik - Anda dapat membuat snapshot database untuk ini, atau membuat beberapa snapshot untuk setiap negara, dan mengaktifkan / menonaktifkan gambar ini baik secara terprogram maupun dengan perintah khusus.
Atau ketika Anda mengembangkan sesuatu di mesin lokal, dan kemudian Anda perlu menyinkronkan keadaan baru dari database di server dengan yang lokal, perbarui server produksi ke keadaan database lokalnya, dan lakukan secara otomatis - Anda juga dapat menerapkan migrasi. Ini sebenarnya tombol biru ini. Basis tahu bahwa kondisinya berbeda dari keadaan kode dan berusaha menyinkronkan status ini. Untuk melakukan ini, tabel khusus dibuat dalam database dengan status yang dikodekan dari struktur database.
Sayangnya, alat ini rumit oleh kenyataan bahwa tidak ada antarmuka visual untuk itu, dan Anda harus bekerja dengannya dari baris perintah. Migrasi nyaman digunakan ketika Anda harus melewati status melalui Git - cukup dengan membuat snapshot basis data seperti file C #. Tetapi alat ini memiliki satu bahaya - jika tidak dikonfigurasi dengan benar, ia dapat menghapus data dalam basis data, jadi Anda harus menggunakannya dengan hati-hati.
Memeriksa ketersediaan database - Visual Studio seharusnya membuatnya

Jika tidak ada database, maka ada yang salah - baik server SQL tidak diinstal, atau sesuatu yang lain, secara umum, seperti lelucon tentang bagaimana jika programmer adalah dokter: "dokter, kaki saya sakit ... - yah, tidak Saya tahu, saya memiliki kaki yang sama dan tidak ada yang sakit! "
Pada titik ini, saya membuat dua string koneksi lagi, appsettings.json mengambil formulir ini:
"ConnectionStrings": { "Habr1_Local": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local;Trusted_Connection=True;MultipleActiveResultSets=true", "Habr1_Test": "Server=(localdb)\\ProjectsV13;Database=Habr1_Test;Trusted_Connection=True;MultipleActiveResultSets=true", "Habr1_Production": "Server=(localdb)\\ProjectsV13;Database=Habr1_Production;Trusted_Connection=True;MultipleActiveResultSets=true" },
Saya membuat komit dan memasukkan snapshot berikut ke dalam repositori:
Snapshot: Snap_1_DataBasesBuat cabang baru, Snap_2_Configurations
tujuan: membuat konfigurasi yang berfungsiSaat mengalihkan konfigurasi, kami dapat mempertimbangkan dari mana saja dalam program ini konfigurasi mana yang saat ini (sebenarnya, bukan dari mana pun, itu tidak akan berfungsi dari View - Anda perlu melakukan fungsi khusus, tetapi ini tidak penting untuk proyek ini):
#if DEBUG DEBUG #else RELEASE ( DEBUG ) #endif
Buka file Startup.cs dan konversikan metode ConfigureServices ke ini:
public void ConfigureServices(IServiceCollection services) { String ConnStr = ""; #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr = "Habr1_Production"; #endif services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString(ConnStr))); services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>(); services.AddControllersWithViews(); services.AddRazorPages(); }
Seperti yang Anda lihat, kami mengganti Habr1_Local dengan variabel, dan sekarang tergantung pada konfigurasi, connectionstring akan menjadi Habr1_Local atau Habr1_Production
Sekarang Anda dapat memulai proyek dan memeriksa bagaimana database dibuat tergantung pada konfigurasi
Kami memilih DEBUG pada panel, memulai, masuk, menerapkan migrasi, memeriksa apakah database telah dibuat (Habr1_Local)
Kami menghentikan proyek, pilih konfigurasi Release, mulai, login, terapkan migrasi, periksa apakah database telah dibuat - kami memiliki 2 pangkalan.
Selesai!
Snapshot: Snap_3_HabrDBTujuan: membuat proyek basis data terpisah, yang kemudian dapat digunakan dalam berbagai proyekMengapa proyek terpisah?
Proyek individual memiliki beberapa keunggulan:
- Mereka dapat digunakan dalam proyek lain.
- Mereka tidak mengkompilasi ulang jika tidak ada perubahan, yang berarti bahwa total waktu kompilasi berkurang
- Proyek individual lebih mudah untuk diuji.
Jadi, tombol kanan pada solusi - tambahkan -> folder solusi baru, beri nama DB.
Lalu langsung ke folder yang dibuat - tambahkan proyek baru -> .net standar, nama HabrDB.
Untuk beberapa alasan, saya membuatnya sebagai .net standard 2.0, saya perlu mengubahnya ke 2.1
(Saat membuatnya menawarkan jalur fisik, biarkan ia berada di folder DB dan secara fisik juga).
Sepertinya ini untuk saya:

Jadi, kami memiliki beberapa ApplicationDBContext dalam proyek ini, dan kami membuat yang lain dari kami sendiri? Apakah mereka akan saling bertentangan? Sekarang kita akan berteman dengan mereka. Kami akan memiliki dua konteks berbeda ke database yang sama, yang tidak akan berpotongan melalui Entity Framework. Kami akan memberi mereka nama skema yang berbeda: satu akan tetap dbo secara default, dan yang lainnya akan "habr".
Jika Anda menghubungkan konteks seperti itu melalui akar komposisi, maka mereka dapat didaur ulang di proyek lain hampir secara transparan. (E.g. konteks gudang dan konteks karyawan)
Dan satu lagi momen arsitektur, kadang-kadang Anda perlu menambahkan beberapa properti Anda ke pengguna, ini tidak berlaku langsung ke topik artikel, tetapi kami akan melakukannya hanya untuk mengetahui bagaimana melakukannya. Selain itu, kami akan dapat membuat dan menghapus konteks secara independen satu sama lain. Ide yang bagus adalah memisahkan tabel keamanan dengan data pribadi dan mengenkripsi data tersebut di tingkat basis data (kami tidak akan melakukan ini dalam proyek ini, tetapi secara umum hal ini kadang diperlukan, termasuk oleh hukum).
Ya, dan lebih mudah untuk menguji dengan cara ini, Anda tidak dapat membuat semua tabel sekaligus, tetapi hanya mereka yang diperlukan untuk menguji konteks yang diberikan.
Saya membuat snapshot proyek baru - tahap 4.
Tujuan fase ini adalah:- ubah pengguna standar menjadi mahir
- ubah pengguna dalam file Srartup.cs
- ubah pengguna di LoginPartial dan ViewImports
- buat migrasi baru untuk secara otomatis membuat database dalam format baru
Jadi, kami mentransfer kelas ApplicationDBContext dari proyek WebApp ke HabrDB.
Itu tidak portabel, hanya disalin. Kami menghapusnya dari WebApp, membukanya dari proyek HabrDB dan mengubah namespace-nya menjadi HabrDB, banyak kesalahan muncul.
Ya, dalam proyek ini tidak ada paket yang diperlukan, sekarang kami akan mengirimkannya.
Melalui nuget, dalam proyek HabrDB, Anda perlu menginstal Microsoft.AspNetCore.Identity.EntityFrameworkCore.

Kami mengklik bola lampu dan menawarkan kami untuk menginstal versi terbaru.
File SecurityDBContext akhir (harus juga diganti namanya) menggunakan formulir ini:
using System; using System.Collections.Generic; using System.Text; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace HabrDB { public class SecurityDBContext : IdentityDbContext { public SecurityDBContext(DbContextOptions<SecurityDBContext> options) : base(options) { } } }
Setelah perakitan, kami
dengan hati-hati memproses file dengan kesalahan, dan memang benar - kami menghapus ApplicationDBContext dan menggantinya dengan SecurityDBContext. Sekarang Anda perlu mengganti semua tautan ke ApplicationDBContext di seluruh proyek dengan SecurityDBContext.
Setelah itu, segitiga kuning muncul di proyek saya pada referensi dari WebApp, yang menunjukkan bahwa beberapa tautan bekerja dengan tidak benar. Saya membersihkan proyek (build -> solusi bersih), menutup proyek, menghapus semua direktori Debug, Release, Obj dan Bin dari folder proyek, setelah itu saya membuka proyek lagi, untuk beberapa waktu saya mencari tautan yang diperlukan di Internet dan memuatnya, dan segitiga, menghilang - maka semuanya baik-baik saja.
Sekarang hapus folder Data dari proyek WebApp, hapus database kami (Habr1_Local dan Habr1_Production) di jendela SQL Server Object Explorer dan jalankan proyek. Kami mencoba masuk - dan sekarang alih-alih tawaran menerapkan migrasi memberikan kesalahan.

Itu benar, kami menghapus folder Data di mana semua migrasi berada dan sekarang kerangka kerjanya tidak tahu harus berbuat apa. Tapi itu sangat keren ?! Kenapa? Lalu apa yang kita sekarang akan memperluas kelas pengguna.
Tambahkan file baru ke proyek HabrDB:
ApplicationUser.cs
Kami mewarisinya dari IdentityUser
using Microsoft.AspNetCore.Identity; using System; using System.Collections.Generic; using System.Text; namespace HabrDB { public class ApplicationUser:IdentityUser { public String NickName { get; set; } public DateTime BirthDate { get; set; } public String PassportNumber { get; set; } } }
Dalam file SecurityDBContext di header kelas tambahkan:
public class SecurityDBContext : IdentityDbContext<ApplicationUser>
Ini diperlukan agar EntityFramework tahu bahwa sekarang saat membuat database Anda perlu menggunakan model pengguna tingkat lanjut dari kelas AppllicationUser alih-alih yang standar.
Metode ConfigureServices dalam file Startup.cs mengambil bentuk:
public void ConfigureServices(IServiceCollection services) { String ConnStr = ""; #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr = "Habr1_Production"; #endif services.AddDbContext<SecurityDBContext>(options => options.UseSqlServer( Configuration.GetConnectionString(ConnStr))); services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<SecurityDBContext>(); services.AddControllersWithViews(); services.AddRazorPages(); }
(Ganti IdentityUser dengan ApplicationUser)
Dalam file _ViewImports.cshtml, tambahkan baris
menggunakan HabrDB
Sekarang semua tampilan akan melihat proyek kami dengan basis dan Anda tidak perlu menulis
menggunakan HabrDB di awal tampilan.
Di file _LoginPartial.cshtml, ubah semua IdentityUser ke ApplicationUser.
menggunakan Microsoft.AspNetCore.Identity
menyuntikkan SignInManager SignInManager
menyuntikkan UserManager UserManager
Untuk berjaga-jaga, kami sedang menyusun proyek untuk mengetahui bahwa kami tidak memiliki kesalahan dan kami tidak lupa mengganti apa pun di mana pun.
Sekarang lakukan migrasi.
Anda perlu membuka Package Manager Console, pilih proyek DB / HabrDB dan tulis untuk itu
add-migration initial
Inilah yang saya dapatkan:

Ayah Migrasi muncul di proyek HabrDB dan ada file di dalamnya yang akan memungkinkan kami membuat basis data kami secara otomatis, tetapi sekarang dengan bidang tambahan kami - NickName, BirthDate, PassportNumber.
Mari kita coba cara kerjanya - mari jalankan dan coba masuk (jangan mendaftar, Anda harus memasukkan kata sandi yang rumit di sana):

Saya ditawari untuk melakukan migrasi dan saya setuju - ini adalah basis kami:

Dengan tahap ini, semuanya
Snapshot: Snap_4_Security sudah siap
Buat tembakan kelima.
Tujuan:- buat string koneksi uji berfungsi
- buat proyek pengujian basis data
- buat proyek uji buat basis dan uji sesuatu yang bermanfaat
Kami klik kanan pada ayah DB, buat proyek baru - MSTest .net core.
Ubah nama satu-satunya file dalam proyek ini menjadi DBTest dan pikirkan ...
.
Masalah
, , ConnectionString (WebApp), - Release/Debug ?.. , Test ?
, β - , Run All Tests β β¦ , β¦
!
HabrDBContext :
using System; using System.IO; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; namespace HabrDB { public class HabrDBContext:DbContext { public String ConnectionString = ""; public IConfigurationRoot Configuration { get; set; } public HabrDBContext CreateTestContext() { DirectoryInfo info = new DirectoryInfo(Directory.GetCurrentDirectory()); DirectoryInfo temp = info.Parent.Parent.Parent.Parent; String CurDir = Path.Combine(temp.ToString(), "WebApp"); String ConnStr = "Habr1_Test"; Configuration = new ConfigurationBuilder().SetBasePath(CurDir).AddJsonFile("appsettings.json").Build(); var builder = new DbContextOptionsBuilder<HabrDBContext>(); var connectionString = Configuration.GetConnectionString(ConnStr); builder.UseSqlServer(connectionString); ConnectionString = connectionString; return this; } } }
Nuget :
Microsoft.AspNetCore.Identity.EntityFrameworkCore Microsoft.EntityFrameworkCore Microsoft.EntityFrameworkCore.SqlServer Microsoft.Extensions.Configuration.FileExtensions Microsoft.Extensions.Configuration.Json
CreateTestContext Connection String Habr1_Test. . reference , builder connectionString, , WebApp, appsettings.json , . connectionString ( ).
, DLL . .
Kenapa begitu
ConnectionString , ConnectionString. : . - , , . , appsettings.json .
- , -.
HabrDB DBClasses, . code first, .
:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text; namespace HabrDB.DBClasses { [Table("Phones", Schema ="Habr")] public class Phone { [Key] public int Id { get; set; } public String Model { get; set; } public DateTime DayZero { get; set; } } }
, «» β «». Table namespace ( SQL Schema).
: , β partial .
partial class HabrDBContext.cs β :
public partial class HabrDBContext:DbContext
HabrDBContext.cs β CTRL+ β CTRL+V , , HabrDBContext_Infrastructure.cs, HabrDBContext_Data.cs
:
using HabrDB.DBClasses; using Microsoft.EntityFrameworkCore; namespace HabrDB { public partial class HabrDBContext:DbContext { public DbSet<Phone> Phones { get; set; } } }
β , . , β .
, !
:
using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; namespace DBTest { [TestClass] public class DBTest { [TestMethod] public void TestMethod1() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = db.Phones.ToList(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; db.Phones.Add(ph); db.SaveChanges(); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } } }
play Test Explorer ( Test -> Run All Tests) β¦

! .
:
Message: Test method DBTest.DBTest.TestMethod1 threw exception: System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
- β¦
, !
HabrDBContext_Infrastructure.cs? !
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { String ConnStr = ""; if (Configuration == null) { #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr= "Habr1_Production"; #endif Configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json").Build(); ConnectionString = Configuration.GetConnectionString(ConnStr); } optionsBuilder.UseSqlServer(ConnectionString); }
β¦
!!!
β !

?
breakpoints OnConfiguring CreateTestContext , CreateTestContext, ConnectionString . . - OnConfiguringβ¦ ? call stack β db.Database.EnsureCreated() ! , β EnsureCreated. , - EnsureCreated. , ( , ) middlware, DI, , β , OnConfiguring β . .
β β¦
?
, β¦ ?
, β¦ - . ? , ?
Snapshot: Snap_5_ContextCraftingDAL β Data Access Layer.
HabrDBContext_Data.cs, HabrDBContext_DAL.cs
:
using HabrDB.DBClasses; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Threading.Tasks; namespace HabrDB { public partial class HabrDBContext:DbContext { public async Task<int> AddPhone(Phone ph) { this.Phones.Add(ph); int res = await this.SaveChangesAsync(); return res; } public async Task<List<Phone>> GetAllPhones() { List<Phone> phones = await this.Phones.ToListAsync(); return phones; } } }
β . , - , . β Data Access Layer. MVC . β ? β ! β .
β . !
!
, .
DI startup.cs, ,
:
[TestMethod] public void AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = db.GetAllPhones().Result; Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); }
, β .
, - . :

db.GetAllPhones().Result? , DAL . , await. , .
async Task β , β await
[TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); }
, , Task void β .
, .
Snapshot: Snap_6_Dal, ? !
β¦
db.Database.EnsureDeleted⦠!
β¦ . , β SQL Management Studio, db.Database.EnsureDeleted , , , , , .
.
, , , , .
: , - , .
db.Database.EnsureDeleted, , , β¦

, β¦
, .
, ( solution) , add -> .net standard C#, Extensions. 2.1 .
Extensions DBContextExtensions.cs :
using Microsoft.EntityFrameworkCore; using System; using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Extensions { public static class DBContextExtensions { public static int EnsureDeleted<TEntity>(this DatabaseFacade db, DbSet<TEntity> set) where TEntity : class { TableDescription Table = GetTableName(set); int res = 0; try { res = db.ExecuteSqlRaw($"DROP TABLE [{Table.Schema}].[{Table.TableName}];"); } catch (Exception) { } return res; } public static TableDescription GetTableName<T>(this DbSet<T> dbSet) where T : class { var dbContext = dbSet.GetDbContext(); var model = dbContext.Model; var entityTypes = model.GetEntityTypes(); var entityType = entityTypes.First(t => t.ClrType == typeof(T)); var tableNameAnnotation = entityType.GetAnnotation("Relational:TableName"); var tableSchemaAnnotation = entityType.GetAnnotation("Relational:Schema"); var tableName = tableNameAnnotation.Value.ToString(); var schemaName = tableSchemaAnnotation.Value.ToString(); return new TableDescription { Schema = schemaName, TableName = tableName }; } public static DbContext GetDbContext<T>(this DbSet<T> dbSet) where T : class { var infrastructure = dbSet as IInfrastructure<IServiceProvider>; var serviceProvider = infrastructure.Instance; var currentDbContext = serviceProvider.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext; return currentDbContext.Context; } } public class TableDescription { public String Schema { get; set; } public String TableName { get; set; } } }
Nuget Microsoft.EntityFrameworkCore
β Microsoft.EntityFrameworkCore.Relational
Extensions β . , β this
public static int EnsureDeleted<TEntity>(this DatabaseFacade db, DbSet<TEntity> set) where TEntity : class
. , , , DatabaseFacade β EnsureDeleted , ! where TEntity: class TEntity constraint β , , - .
β ?
GetTableName, EnsureDeleted, .
?
GetDbContext, GetTableName, β¦
???!!! β¦
DbSet yang kami coba perluas dengan metode GetDbContext di baris tersebut Public static DbContext GetDbContext<T>(this DbSet<T> dbSet) where T : class
, T , class β¦
, β , β EnsureDeleted . . DBContext , , , , , ( GetTableName), , EnsureDeleted β ! removetable - , - SQL β¦ - !
exception EnsureDeleted , , - , .
( )
[TestMethod] public void DeleteTable_Test() { HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureDeleted(db.Phones); }
, , β¦
β .
, .
Snapshot: Snap_7_Extensionsβ HabrDBContext ( ). ( , , . ! ...)
, , . ? , .
HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated();
HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureDeleted(db.Phones);
- β¦
, β¦ , β !
β .
, , , , .
β¦
!
DBTest, β db
using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Extensions; namespace DBTest { [TestClass] public class DBTest { public static HabrDBContext db; [TestMethod] public void AA0_init() { db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); } [TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } [TestMethod] public void DeleteTable_Test() { db.Database.EnsureDeleted(db.Phones); } } }
!
- .
!
!
DBTestBase, DBTest.
DBTest , :
using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Extensions; namespace DBTest { [TestClass] public class DBTest:DBTestBase { [TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } [ClassCleanup] public static void DeleteTable() { db.Database.EnsureDeleted(db.Phones); } } }
DBTestBase:
using HabrDB; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace DBTest { [TestClass] public class DBTestBase { public static HabrDBContext db{ get; set; }
! DBTestBase, , . , β .
[ClassCleanup] β Β«cleanerΒ» β - .
, , , .
, β β - DAL , .
.
, ( ).
DAL , DAL, - , , .
- -.
, .
- , :
T1_AddPhone_Test,
T2_RemovePhone_Test,
..β¦
, β !
! β¦
! !
git repository :
https://github.com/3263927/Habr_1, Identity, Roles, Claims, 3D TypeFilterAttribute ( . ! :/)