Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/database/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/webpack/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 写一篇可测试的文章;“从数据库导入数据”;班_C#_Database_Unit Testing_Class Design_Integration Testing - Fatal编程技术网

C# 写一篇可测试的文章;“从数据库导入数据”;班

C# 写一篇可测试的文章;“从数据库导入数据”;班,c#,database,unit-testing,class-design,integration-testing,C#,Database,Unit Testing,Class Design,Integration Testing,我的任务是从第三方供应商的SQLite数据表中提取所有行,从这些记录中创建业务对象,并将新的业务对象发送到另一个类 伪代码: var databasePath = "%user profile%\application data\some3rdPartyVendor\vendor.sqlite" var connection = OpenSqliteConnection(databasePath); var allGizmoRecords = connection.Query(...); var

我的任务是从第三方供应商的SQLite数据表中提取所有行,从这些记录中创建业务对象,并将新的业务对象发送到另一个类

伪代码:

var databasePath = "%user profile%\application data\some3rdPartyVendor\vendor.sqlite"
var connection = OpenSqliteConnection(databasePath);
var allGizmoRecords = connection.Query(...);
var businessObjects = TransformIntoBizObjs(allGizmoRecords);
someOtherClass.HandleNewBizObjs(businessObjects);
我已经把这些都做好了

我的问题是:如何编写这个类,使它可以进行单元测试?

我应该:

  • 使用存储库模式模拟数据访问
  • 在单元测试中实际提供一个伪SQLite数据库

还有更好的主意吗?我使用的是C#,但这个问题似乎与语言无关。

我认为,这里真正需要测试的是TransformIntoBizObjs,因为连接代码应该在其他地方编写/测试。你需要做的就是简单地传递可能出现的东西,然后看看是否有正确的东西出现


记住测试转换的所有用例,即使是可能不应该在函数调用中结束,但可能会结束的可能奇怪的项。永远不知道人们在他们的数据库中使用了什么。

数据库是复杂的,您需要测试查询代码,并且需要针对真实的sqlite实例进行测试-否则您无法确定您是否遇到了一些罕见的sqlite怪癖或错误

由于测试查询的唯一方法是在一个真正的sqlite文件上运行它,而且在测试中包含这样一个文件非常容易,因此添加另一层只是为了使其“更”可测试或进行“纯”单元测试是没有意义的

只需确保将您能想到的所有奇怪的边缘情况添加到示例文件中。

控制反转(IoC)和依赖注入(DI)将大大有助于提高代码的可测试性。有很多框架可以帮助您实现这一点,但出于您的目的,您不一定需要进行所有这些工作

首先提取一个可能如下所示的接口:

Interface ISqlLiteConnection
{
     public IList<GizmoRecord> Query(...);

}
接口ISqlLiteConnection
{
公共IList查询(…);
}
完成后,应该重构OpenSqlLiteConnection()方法以返回ISqlLiteConnection的实例,而不是具体的实现。要进行测试,只需创建一个实现接口的类,该类模拟实际的数据库查询和连接,并获得确定的结果

您可以非常轻松地注入一个只测试的Sqlite数据库,将代码重构为如下所示。但是,你如何断言结果呢?业务对象被传递到
someOtherClass
。如果您注入一个
isomootherclass
,该类的操作也需要可见。好像有点痛

public class KillerApp
{
    private String databasePath;
    private ISomeOtherClass someOtherClass;

    public KillerApp(String databasePath, ISomeOtherClass someOtherClass)
    {
        this.databasePath = databasePath;
        this.someOtherClass = someOtherClass;
    }

    public void DoThatThing()
    {
        var connection = OpenSqliteConnection(databasePath);
        var allGizmoRecords = connection.Query(...);
        var businessObjects = TransformIntoBizObjs(allGizmoRecords);
        someOtherClass.HandleNewBizObjs(businessObjects);
    }
}

[TestClass]
public class When_Doing_That_Thing
{
    private const String DatabasePath = /* test path */;
    private ISomeOtherClass someOtherClass = new SomeOtherClass();
    private KillerApp app;

    [TestInitialize]
    public void TestInitialize()
    {
        app = new KillerApp(DatabasePath, someOtherClass);
    }

    [TestMethod]
    public void Should_convert_all_gizmo_records_to_busn_objects()
    {
        app.DoThatThing();
        Assert.AreEqual(someOtherClass.Results, /* however you're confirming */);
    }
}
使用
IRepository
将从此类中删除一些代码,从而允许您模拟
IRepository
实现,或者仅为了测试而伪造一个实现

public class KillerApp
{
    private IRepository<BusinessObject> repository;
    private ISomeOtherClass someOtherClass;

    public KillerApp(IRepository<BusinessObject> repository, ISomeOtherClass someOtherClass)
    {
        this.repository = repository;
        this.someOtherClass = someOtherClass;
    }

    public void DoThatThing()
    {
        BusinessObject[] entities = repository.FindAll();
        someOtherClass.HandleNewBizObjs(entities);
    }
}

[TestClass]
public class When_Doing_That_Thing
{
    private const String DatabasePath = /* test path */;
    private IRepository<BusinessObject> repository;
    private ISomeOtherClass someOtherClass = new SomeOtherClass();
    private KillerApp app;

    [TestInitialize]
    public void TestInitialize()
    {
        repository = new BusinessObjectRepository(DatabasePath);
        app = new KillerApp(repository, someOtherClass);
    }

    [TestMethod]
    public void Should_convert_all_gizmo_records_to_busn_objects()
    {
        app.DoThatThing();
        Assert.AreEqual(someOtherClass.Results, /* however you're confirming */);
    }
}

其结果是一个更小的类和一个业务对象和数据层,您可以更轻松地进行更改。您甚至不必模拟数据库调用,您可以配置和初始化ActiveRecord以使用测试数据库(甚至在内存中)。

对,TransformIntoBizObjs是需要测试的。我如何测试知道代码需要打开SQLite db连接?它需要数据库连接?真的不应该。您的步骤应该是1)将记录从SQLite获取到内存中2)将记录转换到新实体中3)享受实体带来的乐趣。在本例中,您只需创建内存中的示例记录,并将它们输入transfomation方法中即可。确切地说,您不需要测试您向我们展示的任何代码。您可以创建一个单独的测试方法,该方法只需自己调用Transform即可。如何/在何处创建该方法取决于您遵循的是哪种测试理念。想象TransformIntoBizObjs正在处理分层数据。这些部件应该测试一下。丹尼尔和库里苏,你们给了我一些好主意。谢谢,我考虑过了。但一些相当明智的开发者说,“不要嘲笑System.Data.*!”我知道Ayende在使用IRepository时很谨慎。我在这里提问之前查阅了那篇文章。我想我可以使用NHibernate或LINQ来创建Sqlite,但这感觉有点过头了——这是一个一次性使用的类(例如file->import…),只与第三方数据库进行一次对话。设置ORM来完成这样一件简单的事情似乎有些过头了。无论如何,谢谢你的回答。我已经投票赞成了。它帮助我解决问题,给了我一些想法,尽管我可能不会选择AR、NHibernate或其他ORM。你怎么做就是你怎么做。而ActiveRecord实际上非常容易开始。我会在这里查看视频。
[ActiveRecord] /* You define your database schema on the object using attributes */
public BusinessObject
{
    [PrimaryKey]
    public Int32 Id { get; set; }

    [Property]
    public String Data { get; set; }

    /* more properties */
}

public class KillerApp
{
    private ISomeOtherClass someOtherClass;

    public KillerApp(ISomeOtherClass someOtherClass)
    {
        this.someOtherClass = someOtherClass;
    }

    public void DoThatThing()
    {
        BusinessObject[] entities = BusinessObject.FindAll() /* built-in ActiveRecord call! */
        someOtherClass.HandleNewBizObjs(entities);
    }
}

[TestClass]
public class When_Doing_That_Thing : ActiveRecordTest /* setup active record for testing */
{
    private ISomeOtherClass someOtherClass = new SomeOtherClass();
    private KillerApp app;

    [TestInitialize]
    public void TestInitialize()
    {
        app = new KillerApp(someOtherClass);
    }

    [TestMethod]
    public void Should_convert_all_gizmo_records_to_busn_objects()
    {
        app.DoThatThing();
        Assert.AreEqual(someOtherClass.Results, /* however you're confirming */);
    }
}