2

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

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

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

我尝试进行单元测试,因为我正在更新很多我想确保每条信息都正确的字段(即不是用名字或类似名称设置的电子邮件地址)。

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

// 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

4 回答 4

2

您可以模拟数据库调用,也可以模拟记录类型并通过模拟内部方法

于 2012-10-04T16:23:12.287 回答
0

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

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

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

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.
        }
    }
}
于 2012-10-04T16:19:38.083 回答
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 为真时调用 Update 或在 logicCheck2 通过但 logicCheck1 未通过时调用 Update3 的能力。您不会模拟您的私有方法,而是使用来自 find 方法(您正在模拟的存储库)的预设输入来练习它们。

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

于 2012-10-04T17:07:32.630 回答
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)
            ...
        }
    }
}
于 2012-10-04T18:46:26.660 回答