如何对此进行单元测试?有很多数据库调用的大型foreach

时间:2012-10-04 16:11:24

标签: c# .net unit-testing nunit moq

我必须创建一个程序,基本上从文件中读取x行,然后尝试在db中找到它,然后获取文件中的一些信息并更新数据库中行的信息。

如果所有其他方法都失败,请将其作为db中的新行插入。

所以基本上有很多插入和更新。

我尝试进行单元测试,因为我要更新很多字段,我想确保每条信息都是正确的位置(即没有使用firstname或类似的东西设置电子邮件地址)。

但是我不知道怎么做,因为我认为这是程序方法

// get records from file

foreach record in file
{
   db record = find if it is in db

   if(record != null)
   {
        if(do another logic check)
         {
             // update record
         }
         else if(do another logic check)
         { 
            // update record
         }
        else
        {
           // do some more logic 

            if(do another check)
             {
               // update record
             }
        }
   }
   else
   {
      // do some more logic checks and do inserts.
   }
}

我在void方法中看到这个,可能有一些私有方法(比如更新记录部分)。现在我应该如何进行单元测试呢?我想对第一个if(做另一个逻辑检查)进行单元测试,如果说我发送的某些记录符合这些条件。

然而,由于那些是私有方法,我无法对它们进行单元测试,现在我没有看到该方法返回任何东西,因为它将经历数百条记录,我可能会将大部分内容打印成一个错误日志文件或其他东西。

我向您展示的代码位于服务层。我会用moq模拟db方法调用。

该应用程序是一个控制台应用程序。

我有什么建议可以更好地解决这个问题,以便检查逻辑吗?

4 个答案:

答案 0 :(得分:2)

您可以模拟db调用,也可以模拟记录类型并传递模拟内部方法

答案 1 :(得分:0)

  

我向您展示的代码位于服务层。我会用moq模拟db方法调用。

这是关键。您应该在您可以在单元测试中模拟的接口后面抽象所有数据库依赖项。您将定义对方法的期望。就读取文件而言,您应该抽象该方法,使其不使用文件名,而是StreamStreamReader,这样您就可以在单元中提供示例文件数据测试而不是依赖于实际文件的存在。

所以你的班级现在可以成为:

public class SomeClass
{
    private readonly IRepository _repo;
    public SomeClass(IRepository repo)
    {
        _repo = repo;
    }

    public void SomeMethod(StreamReader reader)
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            // put your logic and use the _repo to update your database.
        }
    }
}

答案 2 :(得分:0)

你的班级最终可能会从高层看起来像这样:

// get records from file

foreach record in file
{
   dbRecord = _db.Find(record)

   if(dbRecord != null)
   {
        if(logicCheck(dbRecord))
        {
            _db.Update(dbRecord)
        }
        else if(logicCheck2(dbRecord))
        { 
            _db.Update3(dbRecord)
        }
        else
        {
            // do some more logic 
        }
        if(otherCheck(dbRecord))
        {
            _db.Update2(dbRecord)
        }
   }
   else
   {
      // do some more logic checks
      _db.Insert(record)
   }
}

测试看起来像这样:

public void TestInsert()
{
    _mock.Setup(r => r.Find(record).Returns(null))

    class = new class(_mock.Object);
    class.methodToTest(fileRecord);

    _mock.Verify( r => r.Insert(record));
}

public void TestUpdate()
{
    var dbRecordThatPassesLogicCheck = new dbRecord(// initialize for test)
    _mock.Setup(db => db.Find(record).Returns(dbRecordThatPassesLogicCheck))

    class = new class(_mock.Object);
    class.methodToTest(fileRecord);

    _mock.Verify( r => r.Update(dbRecordThatPassesLogicCheck));
}

public void TestUpdate3()
{
    var dbRecordThatPassesLogicCheck2 = new dbRecord(// initialize for test)
    _mock.Setup(db => db.Find(record).Returns(dbRecordThatPassesLogicCheck2))

    class = new class(_mock.Object);
    class.methodToTest(fileRecord);

    _mock.Verify( db => db.Update3(dbRecordThatPassesLogicCheck2));
}

Moq(或任何其他模拟框架)在这里购买的是能够模拟类如果logicCheck为true则调用Update,或者如果logicCheck2通过则调用Update3但是logicCheck1没有。你没有模拟你的私有方法,你可以使用来自find方法(你正在嘲笑它的存储库)的预设输入来练习它们。

你的类应该采取第二个接口依赖于初始记录获取的检索,这将使代码更加分离(现在如果文件系统读取更改为Web服务读取类不会直接受到影响)并允许更多可测试性。

答案 3 :(得分:0)

我们倾向于使用许多带有注入依赖项的小类。这提供了可用性和可测试性接缝。我们也倾向于提取if .. else if .. else阻塞到可注入的Strategy对象 - 这使得它们的逻辑可以在使用它们的进程之外进行测试。

这分离了阅读文件的顾虑

class RecordFileReader
{
    void UpdateAll(file)
    {
        foreach (var record in file)
        {
            _fileRecordImporter.Import(record)
        }
    }
}

处理单个文件记录

class FileRecordImporter
{   
    void Import(record)
    {
        db_record = find if it is in db
        if (db_record != null)
        {
            _dbRecordUpdater.Update(db_record, record)
            return;
        }
        _dbRecordCreator.CreateFrom(record);
    }
}

并确定如何将信息从记录传输到数据库以进行更新

class DbRecordUpdater
{
    void Update(db_record, record)
    {
        var recordUpdater = _recordUpdaters.FirstOrDefault(x=>x.IsMatch(db_record, record));
        if (recordUpdater != null)
        {
            recordUpdater.Update(db_record, record)
        }
    }
}

并插入

class DbRecordCreator
{
    void CreateFrom(record)
    {
        var recordCreator = _recordCreators.FirstOrDefault(x=>x.IsMatch(record));
        if (recordCreator != null)
        {
            var db_record = recordCreator.Create(record)
            ...
        }
    }
}