Visual Studio在线单元测试和localDb

Axe*_*xel 9 localdb azure-devops

我有一个基于Visual Studio Online的解决方案.此解决方案中的所有单元测试都需要一个数据库,我试图让构建使用测试的专用localdb(通过在测试项目中添加一个light mdf文件并使用localdb连接字符串)但是它失败并出现此错误(evrything工作正常)在我的桌面上):

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" /> 
Run Code Online (Sandbox Code Playgroud)

我首先使用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());


}
}
Run Code Online (Sandbox Code Playgroud)

使用unitOfWorkScope实例化DbContext:

    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);
    }
}
Run Code Online (Sandbox Code Playgroud)

允许我在测试中(以及在应用程序中)做这样的事情

       [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);

        }
    }
Run Code Online (Sandbox Code Playgroud)

这个场景甚至可能吗?还有其他办法吗?

谢谢

Dav*_*son 13

这很可能与在VSO构建服务器上初始化LocalDb有关,而VSO构建服务器是为了运行构建而运行的.

根据https://github.com/ritterim/automation-sql,可以安装LocalDb,但不能初始化.

已安装LocalDB但无法连接

您可能已安装LocalDB,但从未在计算机上初始化该实例.通过命令提示符运行此命令.

LocalDB SQL EXPRESS 2014

"C:\ Program Files\Microsoft SQL Server\120\Tools\Binn\SqlLocalDB.exe"create"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数据库,以便运行单元测试.

我正在使用RimDev.Automation.Sql nuget包(http://www.nuget.org/packages/RimDev.Automation.Sql/)来允许我的测试以编程方式创建数据库.

我还希望与Unity和Entity Framework 6集成,以便尽可能地模拟我在生产中的数据库访问.为此,我创建了一个基于我所有测试继承的基类.我想要的一件事是我的所有测试共享同一个数据库,所以我将LocalDb创建为静态.

此外,初始化在Resources文件夹中运行一组脚本,以便根据需要使用库存数据预填充数据库.您需要确保将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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个示例测试:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在我的测试项目中,我有测试实体对象.

using Playground.Model;

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

        public PlaygroundEntitiesTest(string connectionString) : base(connectionString)
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的模型项目中,我有我的实体和我的上下文.

Playground.cs

using System;

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

        public string Name { get; set; }

        public string Location { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我在单元测试项目中设置了一个构建后步骤来执行命令以初始化LocalDb,如下所示:

后期构建的项目设置 完整的命令是

"C:\ Program Files\Microsoft SQL Server\120\Tools\Binn\SqlLocalDB.exe"create"v12.0"12.0 -s

然后,就像推送到Visual Studio Online并启动我的构建一样简单.

在此输入图像描述

  • 这个答案的重点是Post构建事件.答案的其余部分可以删除,因为它没有真正回答问题,而且更有说服力而不是有用. (4认同)
  • @kjbartel如果您阅读了我的答案的开头,您将看到初始化命令已被识别.对我的具体解决方案的进一步解释是为了帮助其他人.此外,能够以编程方式控制LocalDb并设置数据库对于在源代码管理中构建没有二进制文件(如mdf)的强大单元测试解决方案非常有用.你以什么方式发现它令人困惑?有什么我可以进一步解释,以使其更有帮助吗? (4认同)