Testowanie za pomocą SQLiteTesting with SQLite

SQLite ma tryb w pamięci, która pozwala na potrzeby zapisu testy względem relacyjnej bazy danych, bez potrzeby operacje rzeczywistej bazy danych SQLite.SQLite has an in-memory mode that allows you to use SQLite to write tests against a relational database, without the overhead of actual database operations.

Porada

Można wyświetlić w tym artykule próbki w witrynie GitHubYou can view this article's sample on GitHub

Przykładowy scenariusz testowychExample testing scenario

Należy wziąć pod uwagę następujące usługi, która umożliwia aplikacji kod, aby wykonać pewne operacje związane z blogów.Consider the following service that allows application code to perform some operations related to blogs. Wewnętrznie używa DbContext które nawiązuje połączenie z bazą danych programu SQL Server.Internally it uses a DbContext that connects to a SQL Server database. Należałoby wymiany z tym kontekstem do połączenia z bazą danych SQLite w pamięci, tak aby nie możemy zapisać wydajne testów dla tej usługi bez konieczności modyfikowania kodu lub dużo pracy w celu utworzenia testu podwójne kontekstu.It would be useful to swap this context to connect to an in-memory SQLite database so that we can write efficient tests for this service without having to modify the code, or do a lot of work to create a test double of the context.

using System.Collections.Generic;
using System.Linq;

namespace BusinessLogic
{
    public class BlogService
    {
        private BloggingContext _context;

        public BlogService(BloggingContext context)
        {
            _context = context;
        }

        public void Add(string url)
        {
            var blog = new Blog { Url = url };
            _context.Blogs.Add(blog);
            _context.SaveChanges();
        }

        public IEnumerable<Blog> Find(string term)
        {
            return _context.Blogs
                .Where(b => b.Url.Contains(term))
                .OrderBy(b => b.Url)
                .ToList();
        }
    }
}

Przygotowanie kontekstGet your context ready

Należy unikać konfigurowania dwóch dostawców bazy danychAvoid configuring two database providers

W testach zamierzasz skonfigurować zewnętrznie kontekst do użycia dostawcy InMemory.In your tests you are going to externally configure the context to use the InMemory provider. Jeśli konfigurujesz dostawcy bazy danych przez zastąpienie OnConfiguring w kontekstu, następnie należy dodać kodu warunkowego, upewnij się, tylko Konfigurowanie dostawcy bazy danych, jeśli nie ma już skonfigurowany.If you are configuring a database provider by overriding OnConfiguring in your context, then you need to add some conditional code to ensure that you only configure the database provider if one has not already been configured.

Porada

Jeśli używasz platformy ASP.NET Core powinien nie należy tego kodu od dostawcy bazy danych skonfigurowano poza kontekstem (w pliku Startup.cs).If you are using ASP.NET Core, then you should not need this code since your database provider is configured outside of the context (in Startup.cs).

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFProviders.InMemory;Trusted_Connection=True;ConnectRetryCount=0");
    }
}

Dodaj Konstruktor do testowaniaAdd a constructor for testing

Najprostszym sposobem, aby umożliwić testowanie z innej bazy danych jest zmodyfikowanie kontekst do udostępnienia konstruktora akceptującego DbContextOptions<TContext>.The simplest way to enable testing against a different database is to modify your context to expose a constructor that accepts a DbContextOptions<TContext>.

public class BloggingContext : DbContext
{
    public BloggingContext()
    { }

    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }

Porada

DbContextOptions<TContext>Określa, że kontekst wszystkie jego ustawienia, takie jak bazy danych do nawiązania połączenia.DbContextOptions<TContext> tells the context all of its settings, such as which database to connect to. Jest to ten sam obiekt skompilowanego za pomocą metody OnConfiguring w kontekście użytkownika.This is the same object that is built by running the OnConfiguring method in your context.

Pisanie testówWriting tests

Kluczem do testowania przy użyciu tego dostawcy jest możliwość Poinformuj kontekstu SQLite i kontrolowanie zakresu bazy danych w pamięci.The key to testing with this provider is the ability to tell the context to use SQLite, and control the scope of the in-memory database. Zakres bazy danych jest kontrolowana przez otwarcie i zamknięcie połączenia.The scope of the database is controlled by opening and closing the connection. Bazy danych jest zakresem czasu trwania, że połączenie jest otwarte.The database is scoped to the duration that the connection is open. Zwykle ma czystą bazy danych dla każdej metody.Typically you want a clean database for each test method.

using BusinessLogic;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;

namespace TestProject.SQLite
{
    [TestClass]
    public class BlogServiceTests
    {
        [TestMethod]
        public void Add_writes_to_database()
        {
            // In-memory database only exists while the connection is open
            var connection = new SqliteConnection("DataSource=:memory:");
            connection.Open();

            try
            {
                var options = new DbContextOptionsBuilder<BloggingContext>()
                    .UseSqlite(connection)
                    .Options;

                // Create the schema in the database
                using (var context = new BloggingContext(options))
                {
                    context.Database.EnsureCreated();
                }

                // Run the test against one instance of the context
                using (var context = new BloggingContext(options))
                { 
                    var service = new BlogService(context);
                    service.Add("http://sample.com");
                }

                // Use a separate instance of the context to verify correct data was saved to database
                using (var context = new BloggingContext(options))
                {
                    Assert.AreEqual(1, context.Blogs.Count());
                    Assert.AreEqual("http://sample.com", context.Blogs.Single().Url);
                }
            }
            finally
            {
                connection.Close();
            }
        }

        [TestMethod]
        public void Find_searches_url()
        {
            // In-memory database only exists while the connection is open
            var connection = new SqliteConnection("DataSource=:memory:");
            connection.Open();

            try
            {
                var options = new DbContextOptionsBuilder<BloggingContext>()
                    .UseSqlite(connection)
                    .Options;

                // Create the schema in the database
                using (var context = new BloggingContext(options))
                {
                    context.Database.EnsureCreated();
                }

                // Insert seed data into the database using one instance of the context
                using (var context = new BloggingContext(options))
                {
                    context.Blogs.Add(new Blog { Url = "http://sample.com/cats" });
                    context.Blogs.Add(new Blog { Url = "http://sample.com/catfish" });
                    context.Blogs.Add(new Blog { Url = "http://sample.com/dogs" });
                    context.SaveChanges();
                }

                // Use a clean instance of the context to run the test
                using (var context = new BloggingContext(options))
                {
                    var service = new BlogService(context);
                    var result = service.Find("cat");
                    Assert.AreEqual(2, result.Count());
                }
            }
            finally
            {
                connection.Close();
            }
        }
    }
}