单一责任原则,测试驱动开发和功能设计

时间:2016-01-27 02:29:21

标签: tdd solid-principles

我是测试驱动开发的新手,我刚刚开始学习SOLID原理,所以我希望有人可以帮助我。在开发TDD范例中的单元测试和方法的背景下,我在理解单一责任原则方面遇到了一些概念上的困难。例如,假设我想开发一种从数据库中删除项目的方法 - 在我的代码看起来如下之前......

我从定义测试用例开始:

"Delete_Item_ReturnsTrue": function() {
    //Setup
    var ItemToDelete = "NameOfSomeItem";
    //Action
    var BooleanResult = Delete( ItemToDelete );
    //Assert
    if ( BooleanResult === true ) {
        return true;
    } else {
        console.log("Test: Delete_Item_ReturnsTrue() - Failed.");
        return false;
    }
} 

我运行测试以确保失败,然后我开发了这种方法......

function Delete( ItemToDelete ) {
    var Database = ConnectToDatabase();
    var Query = BuildQuery( ItemToDelete );
    var QueryResult = Database.Query( Query );
    if ( QueryResult.error !== true ) {
    //if there's no error then...
       return true;
    } else {
       return false;
    }
}

如果我正确理解单一责任原则,我最初编写的方法有责任删除项目,如果没有错误则返回true。因此,如果我遵循SOLID范例,原始方法应该重构为类似......

function Delete( WhatToDelete, WhereToDeleteItFrom ) {
     WhereToDeleteItFrom.delete( WhatToDelete );
}

如此设计,将我的方法从布尔函数改为void函数,所以现在我无法以与测试旧方法相同的方式测试新方法。

我想我可以测试抛出的异常,但是它不会使它成为集成测试而不是单元测试,因为方法中没有抛出实际的异常?

我是否设计并实现了一个额外的功能,用于检查数据库以便在我的单元测试中使用?

我是不是因为它无效而不测试它?这在TDD中究竟有用吗?

我是否会传入模拟存储库,然后在状态发生变化后将其返回?是不是只是将我带回了原点?

我是否传入对模拟存储库的引用,然后在方法完成后对存储库进行测试?虽然不会被视为副作用吗?

1 个答案:

答案 0 :(得分:2)

所以单一责任原则说:当我需要改变一个功能时,它们应该是一个原因。

让我们来看看你的功能:

function Delete( ItemToDelete ) {
    var Database = ConnectToDatabase();
    var Query = BuildQuery( ItemToDelete );
    var QueryResult = Database.Query( Query );
    if ( QueryResult.error !== true ) {
    //if there's no error then...
       return true;
    } else {
       return false;
    }
}
  • 数据库更改:ConnectToDatabese()需要更改,但不需要此功能
  • db结构的变化:BuildQuery()需要更改(可能)
  • ...

所以在第一眼看来,你的功能看起来不错。某些地方的命名有点令人困惑。函数应该以小写字母开头。例如" connectToDatabase()"。 connectToDatabase返回一个Object有点令人惊讶。

名称BuildQuery似乎是错误的,因为BuildQuery(myItem)返回一个删除内容的查询。

但我永远不会有这么长的复杂功能只有一个测试用例。

您需要编写更多测试用例。 对于第一个测试用例,您可以编写如下函数:

function Delete( ItemToDelete) {
  return true
}

下一个测试用例可能是"使用项目ID"调用buildDeleteQuery函数。那时你需要考虑如何调用你的数据库。如果你有一个dbObject,那就可以模拟那个Object。

function Delete( ItemToDelete ) {
   buildDeleteQuery( ItemToDelete.id )
   return true
}

现在越来越多的测试用例。请记住,还会有buildDeleteQuery的测试用例。但是对于外部函数,你可以模拟buildDeleteQuery。

现在回答你的一些问题:

  • 当您第一次编写测试然后编写代码时,您将拥有可测试的代码。
  • 该代码看起来不同,因为测试应该不复杂。当您想要简单的测试时,您将自动拥有更小的功能。
  • 是的,您将为您调用的每个函数编写一个测试。
  • 也许你会模仿物体。如果你不能在没有包装功能的情况下测试你的数据库调用,你将创建一个函数,它允许你测试该功能。
  • 请记住,您的测试是代码的文档。所以尽量保持简单。

但最重要的是:继续练习!当您从TDD开始时需要一些时间。一个好的和有趣的资源是:Uncle Bobs Bowling Kata只需从网站下载幻灯片,看看tdd是如何逐步完成的。