Модульный онлайн-тест Visual Studio и localDb

У меня есть решение, построенное на Visual Studio Online. Для всех модульных тестов в этом решении требуется база данных, я попытался заставить сборку использовать выделенную тестовую локальную базу данных (путем добавления легкого файла mdf в тестовый проект и использования строки подключения к локальной базе данных), но это не удается с этой ошибкой (все работает нормально на моем рабочем столе):

System.Data.SqlClient.SqlException: истекло время ожидания подключения. Время ожидания истекло при попытке использовать подтверждение рукопожатия перед входом в систему. Это может быть связано с ошибкой рукопожатия перед входом в систему или с тем, что сервер не смог ответить вовремя. Продолжительность попыток подключения к этому серверу составила - [Pre-Login] initialization=29460; рукопожатие=161; ---› System.ComponentModel.Win32Exception: время ожидания операции истекло.

ИЗМЕНИТЬ

Строка подключения

<add name="TestDb" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=AeroBase;Integrated Security=true;AttachDBFilename=|DataDirectory|\App_Data\AeroBase.mdf" providerName="System.Data.SqlClient" /> 

Сначала я использую код EF6, репозиторий и шаблоны единиц работы для доступа к нему. Вот DbContext:

public class AeroDataContext : DbContext, IDbContext
{
private Guid DataContextId;
private string _name;
public string Name { get { return _name; } }

public AeroDataContext(string cnxStringName, string cnxString)
    : base(cnxString)
{
    this.Database.Log = delegate(String name)
                        {
                           // Debug.WriteLine(name);
                        };
    _name = cnxStringName;
    this.Configuration.LazyLoadingEnabled = false;
    DataContextId = Guid.NewGuid();
    Debug.WriteLine("AeroDbCreation Id = " + DataContextId.ToString());


}
}

DbContext создается с использованием unitOfWorkScope:

    public class UnitOfWorkFactory : IUnitOfWorkFactory
{

    private string _cnxStringName;
    private string _cnxString;

    public UnitOfWorkFactory(string cnxStringName, string cnxString)
    {
        _cnxString = cnxString;
        _cnxStringName = cnxStringName;
    }

    public IUnitOfWorkScope GetUnitOfWorkScope(bool disposeAtEndOfContext = true)
    {

        return new UnitOfWorkScope(new AeroDataContext(_cnxStringName, _cnxString), disposeAtEndOfContext);
    }
}

что позволяет мне делать подобные вещи в тесте (и в приложении)

       [TestMethod]
    public void DB_Get_LFFE_Airport_By_ICAOInclude_SubType()
    {
        //structuremap container built in the TestInitilized method
        IUnitOfWorkFactory _uowf = container.GetInstance<IUnitOfWorkFactory>();
        using (IUnitOfWorkScope uows = _uowf.GetUnitOfWorkScope(true))
        {

            IAirportRepository repo = uows.GetRepository<IAirportRepository>();
            Airport ar = repo.SearchByICAO("LFFE").FirstOrDefault();
            AirportValidator.LFFE(ar);
           
        }
    }

Такой сценарий вообще возможен? есть ли другой способ сделать это?


person Axel    schedule 20.04.2014    source источник
comment
LocalDB установлен на серверах сборки: listofsoftwareontfshostedbuildserver.azurewebsites.net. Возможно, вам нужно поделиться своим кодом для подключения к Локальная БД, и мы можем проверить, есть ли у нее очевидные проблемы, которые не позволяют ей работать где угодно, кроме вашей машины?   -  person DaveShaw    schedule 25.04.2014
comment
Спасибо, Дэйв, за ссылку, я изменил свой пост, чтобы добавить код. Это вызвало два вопроса: localDb на серверах сборки также имеет версию 11.0? и эта строка подключения предполагает, что пользователь, запускающий тест на сервере, также может войти в локальную базу данных?   -  person Axel    schedule 25.04.2014
comment
Я предполагаю, что, поскольку у вас есть учетная запись, на которой запущена размещенная сборка TFS, нет доступа к локальной базе данных.   -  person DaveShaw    schedule 25.04.2014
comment
Да, но в этом случае у меня должно быть исключение с отказом в подключении или что-то в этом роде...   -  person Axel    schedule 25.04.2014
comment
Привет, Аксель, ты нашел решение? У меня такая же проблема... спасибо!   -  person Jorge Fioranelli    schedule 01.10.2014
comment
Нет, если честно, я отказался. Я больше не использую TU, связанный с базой данных, в сборке. Одним из решений может быть установка базовой БД, предназначенной для TU, в Azure.   -  person Axel    schedule 01.10.2014
comment
@JorgeFioranelli Смотрите мой ответ ниже, он должен решить вашу проблему. Прежде чем я добавил шаг события после сборки, я получил это исключение, а также другое общее исключение соединения sql.   -  person Dave Ferguson    schedule 18.03.2015


Ответы (1)


Скорее всего, это связано с тем, что LocalDb не инициализирован на сервере сборки VSO, который запускается для запуска сборки.

Согласно https://github.com/ritterim/automation-sql, LocalDb может быть установлен, но не инициализирован.

LocalDB установлен, но не может подключиться

У вас может быть установлен LocalDB, но вы никогда не инициализировали экземпляр на своем компьютере. Запустите эту команду через командную строку.

Локальная БД SQL EXPRESS 2014

"C:\Program Files\Microsoft SQL Server\120\Tools\Binn\SqlLocalDB.exe" создать "v12.0" 12.0 -s LocalDB SQL Express 2012

"C:\Program Files\Microsoft SQL Server\110\Tools\Binn\SqlLocalDB.exe" create "v11.0" 11.0 -s Убедитесь, что команда работает, используя SQL Server Management Studio для подключения к экземпляру.

Мое решение:

Каждый раз, когда VSO запускает сборку, она создает новую виртуальную машину из шаблона для запуска сборки. В моем случае я хотел динамически создать небольшую базу данных LocalDb, чтобы запускать модульные тесты.

Я использую пакет nuget RimDev.Automation.Sql (http://www.nuget.org/packages/RimDev.Automation.Sql/), чтобы мои тесты могли программно создавать базу данных.

Я также хотел интегрироваться с Unity и Entity Framework 6, чтобы он максимально точно имитировал мой доступ к базе данных в рабочей среде. Для этого я создал базовый класс, от которого наследуются все мои тесты. Я хотел, чтобы все мои тесты использовали одну и ту же базу данных, поэтому я сделал создание LocalDb статическим.

Кроме того, инициализация запускает набор сценариев в папке «Ресурсы», чтобы предварительно заполнить базу данных данными о запасах, если вы того пожелаете. Вам необходимо убедиться, что сценарии sql помечены для копирования в выходную папку (см. свойства), чтобы файлы находились в правильном пути при выполнении модульного теста.

Если вы хотите создать новую базу данных для каждого тестового класса или даже для каждого теста, это можно легко изменить.

using System;
using System.Data.Entity;
using System.IO;
using Microsoft.Practices.Unity;
using Playground.Model;
using RimDev.Automation.Sql;

namespace Playground.UnitTests
{
    public abstract class TestBaseClass
    {
        // For now, these are static.  We may want to change them at some point
        // to be per class so we create separate databases for each class run, but
        // for now, let's not do that for speed sake.
        private static readonly IUnityContainer _container = new UnityContainer();
        private static bool _isRegistered = false;
        private static readonly object _syncRoot = new object();
        private static LocalDb LocalDb = new LocalDb(databaseName: "PlaygroundTestDb", databasePrefix: "pg", version: "v12.0");
        private static bool _isInitialized = false;

        protected TestBaseClass()
        {
            RegisterComponents(_container);
            InitializeData();
            _container.BuildUp(GetType(), this);
        }

        private void InitializeData()
        {
            lock (_syncRoot)
            {
                if (!_isInitialized)
                {
                    var dbContext = _container.Resolve<PlaygroundEntities>();
                    Database.SetInitializer(
                        new MigrateDatabaseToLatestVersion<PlaygroundEntities, Playground.Model.Migrations.Configuration>());

                    // Make sure database exists.
                    dbContext.Database.Initialize(true);

                    foreach (
                        var f in Directory.GetFiles(Path.Combine(Environment.CurrentDirectory, "Resources"), "*.sql"))
                    {
                        dbContext.Database.ExecuteSqlCommand(File.ReadAllText(f));
                    }
                }
                _isInitialized = true;
            }
        }

        private void RegisterComponents(IUnityContainer container)
        {
            lock (_syncRoot)
            {
                if (!_isRegistered)
                {
                    // WARNING!  Most methods in the unity container are not thread safe.  See http://unity.codeplex.com/discussions/27496
                    // We may need to expose protected methods to register certain types.  For now, assume all of the
                    // tests use the same injected objects.  If a test REALLY needs to a different dependency, the test can
                    // manually create it as well.
                    container.RegisterType<PlaygroundEntities, PlaygroundEntitiesTest>(new TransientLifetimeManager(),
                        new InjectionConstructor(new object[] {LocalDb.ConnectionString}));
                }
                _isRegistered = true;
            }
        }
    }
}

Вот пример теста:

using System.Linq;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Playground.Model;

namespace Playground.UnitTests
{
    [TestClass]
    public class UnitTest1 : TestBaseClass
    {
        [Dependency]
        public PlaygroundEntities Db { get; set; }

        private static bool _initialized = false;

        [TestInitialize]
        public void TestInitialize()
        {
            if (!_initialized)
            {
                Db.Playgrounds.Add(new Playground.Model.Playground() {Name = "Dave's playground", Location = "SomeTown"});
                Db.SaveChanges();
                _initialized = true;
            }
        }

        [TestMethod]
        public void TestMethod1()
        {
            var p = Db.Playgrounds.FirstOrDefault(pg => pg.Name == "Dave's playground");
            Assert.IsNotNull(p);
        }

        [TestMethod]
        public void TestMethod2()
        {
            var p = Db.Playgrounds.FirstOrDefault(pg => pg.Location == "SomeTown");
            Assert.IsNotNull(p);
        }
    }
}

Наконец, в моем тестовом проекте у меня есть объект тестовых сущностей.

using Playground.Model;

namespace Playground.UnitTests
{
    public class PlaygroundEntitiesTest : PlaygroundEntities
    {
        private PlaygroundEntitiesTest()
        {
        }

        public PlaygroundEntitiesTest(string connectionString) : base(connectionString)
        {
        }
    }
}

В моем проекте моделей у меня есть моя сущность и мой контекст.

Детская площадка.cs

using System;

namespace Playground.Model
{
    public class Playground
    {
        public Guid Id { get; set; }

        public string Name { get; set; }

        public string Location { get; set; }
    }
}

PlaygroundEntities.cs

using System.Data.Entity;

namespace Playground.Model
{
    public class PlaygroundEntities : DbContext
    {
        public PlaygroundEntities() : base("PlaygroundConnectionString")
        {
        }

        public PlaygroundEntities(string connectionString) : base(connectionString)
        {
        }

        public virtual DbSet<Playground> Playgrounds { get; set; }
    }
}

Наконец, я настроил этап после сборки в проекте модульного тестирования, чтобы выполнить команду для инициализации LocalDb следующим образом:

Настройки проекта для пост-сборкиПолная команда

"C:\Program Files\Microsoft SQL Server\120\Tools\Binn\SqlLocalDB.exe" создать "v12.0" 12.0 -s

Затем это было так же просто, как нажать на Visual Studio Online и запустить мою сборку.

введите здесь описание изображения

person Dave Ferguson    schedule 18.03.2015
comment
Прикольно.. Постараюсь как можно скорее - person Axel; 18.03.2015
comment
Важным моментом в этом ответе является событие Post build. Остальную часть ответа можно удалить, поскольку на самом деле он не отвечает на вопрос и скорее сбивает с толку, чем помогает. - person kjbartel; 25.05.2015
comment
@kjbartel Если вы прочитаете начало моего ответа, вы увидите, что команда инициализации была идентифицирована. Дальнейшее объяснение моего конкретного решения предназначено для помощи другим. Кроме того, возможность управлять LocalDb и программно настраивать базу данных чрезвычайно полезна для создания надежного решения модульного тестирования без двоичного файла, такого как mdf, в системе управления версиями. В чем вы нашли это запутанным? Есть ли что-то, что я мог бы объяснить подробнее, чтобы сделать его более полезным? - person Dave Ferguson; 26.05.2015
comment
Я получаю следующую ошибку, когда тест пытается получить доступ к БД. System.Data.SqlClient.SqlException : значение sp_configure «аутентификация автономной базы данных» должно быть установлено равным 1, чтобы создать автономную базу данных. Возможно, вам придется использовать RECONFIGURE, чтобы установить значение_в_использовании. Любые идеи? - person NER1808; 14.02.2017