Unit testing 关于测试驱动开发的庸俗问题

Unit testing 关于测试驱动开发的庸俗问题,unit-testing,testing,tdd,Unit Testing,Testing,Tdd,我一直对测试驱动的开发感兴趣,但当我在实际项目中尝试它时,我永远无法坚持到底。我有几个哲学问题在我尝试时不断出现: 你如何处理大的变化?当涉及到测试单个功能(一些参数、结果值、很少的副作用)时,TDD是一个不需要动脑筋的工具。但是,当您需要彻底检修一些大型的东西时,比如从SAX解析库切换到DOM解析库,又该怎么办呢?当代码处于中间状态时,如何保持测试代码重构周期?一旦您开始进行更改,您将得到一堆失败的测试,直到您完全完成大修(除非您在完成转换之前维护某种同时使用DOM和SAX的混合类,但这非常奇

我一直对测试驱动的开发感兴趣,但当我在实际项目中尝试它时,我永远无法坚持到底。我有几个哲学问题在我尝试时不断出现:

  • 你如何处理大的变化?当涉及到测试单个功能(一些参数、结果值、很少的副作用)时,TDD是一个不需要动脑筋的工具。但是,当您需要彻底检修一些大型的东西时,比如从SAX解析库切换到DOM解析库,又该怎么办呢?当代码处于中间状态时,如何保持测试代码重构周期?一旦您开始进行更改,您将得到一堆失败的测试,直到您完全完成大修(除非您在完成转换之前维护某种同时使用DOM和SAX的混合类,但这非常奇怪)。在这种情况下,小步测试代码重构周期会发生什么变化?在整个过程中,您将不再以小的、经过充分测试的步骤移动。人们一定有办法解决这个问题
  • 当使用mock测试GUI或数据库代码时,您真正测试的是什么?mock是为了返回您想要的答案而构建的,那么您如何知道您的代码将与真实世界的数据库一起工作呢?自动化测试对这类事情有什么好处?它在一定程度上提高了可信度,但是a)它并没有给你一个完整的单元测试应有的可信度,b)在某种程度上,你不是简单地验证你的假设与你的代码一起工作,而不是验证你的代码与DB或GUI一起工作吗
  • 有人能给我指出在大型项目中使用测试驱动开发的好案例吗?令人沮丧的是,我基本上只能找到单个类的TDD示例

    谢谢

    使用测试GUI或数据库代码时 mocks,你到底在测试什么? mock的构建是为了精确地返回 回答你想要的,你怎么知道 您的代码将与 真实世界的数据库?问题是什么 自动测试的好处 什么事?它提高了信心 有点,但是a)它不给你 信心的水平与 应完成单元测试,且b)应 在某种程度上,你不是很简单吗 验证您的假设是否有效 用你的代码,而不是你的 代码与DB或GUI一起工作

    这是我的方法:对于数据库访问层(DAL),我不在单元测试中使用mock。相反,我在真实的数据库上运行测试,尽管与生产数据库不同。所以在这个意义上,你们可以说我不在数据库上运行单元测试。对于NHibernate应用程序,我使用相同的模式维护两个数据库,但数据库类型不同(这样做很容易)。我使用它进行自动测试,并使用一个真正的MySQL或SQL server数据库进行临时测试

    我只有一次使用mock对DAL进行单元测试;那时我使用强类型数据集作为ORM(一个大错误!)。我这样做的方式是返回完整表的模拟副本,以便我可以对其执行
    select*
    。后来当我回首往事时,我希望我永远不要这样做,但那是很久以前的事了,我希望我使用了一个合适的ORM


    对于GUI,可以对GUI交互进行单元测试。我这样做的方式是使用来分离模型、视图和演示者。实际上,对于这种类型的应用程序,我只在Presenter和模型上进行测试,其中我使用Typemock(或)来隔离不同的层,以便一次只能集中在一个层上。我不测试视图,但我经常测试Presenter(大多数交互和bug都发生在这里)

    在处理大的变化方面。。。TDD的目的是测试代码的行为以及它如何与所依赖的服务交互。如果您希望使用TDD,并且您正在从DOM解析器迁移到SAX解析器,并且您正在自己编写SAX解析器,那么您将编写测试,根据已知输入(即XML文档)验证SAX解析器的行为。SAX解析器可能依赖于辅助对象的集合,这些对象实际上可以在最初模拟出来,以测试SAX解析器的行为。当您准备好为helper对象编写实现代码时,就可以基于已知输入围绕其预期行为编写测试。在SAX解析器的示例中,您将编写单独的类来实现此行为,以便不干扰依赖于DOM解析器的现有代码。实际上,您可以创建一个IXMLParser接口,由DOM解析器和SAX解析器实现,以便您可以随意切换它们

    就使用Mock或stub而言,使用Mock或stub的原因是您对测试Mock或stub的内部工作不感兴趣,但您对测试依赖于Mock或stub的内部工作感兴趣,而这正是您从单元角度真正测试的。如果您对编写集成测试感兴趣,那么您应该编写集成测试,而不是单元测试。我发现以TDD的方式编写代码对于帮助我定义代码的结构和组织非常有用,因为我要提供的行为是围绕代码的结构和组织的


    我不熟悉任何现成的案例研究,但我确信它们确实存在

    至于数据库角度,正如Ngu Soon Hui提到的,您应该(IMHO)使用类似的东西,它将在已知配置中设置数据库(以便您可以测试预期的结果),但实际应用程序将使用真实的数据库

    对于较大的更改,我建议创建一个分支,并允许测试失败。这将为您提供一个需要更改的区域的待办事项列表,并且可能是
    // Production code in class UserFormController:
    
    void changeUserNameButtonClicked() {
      String newName = nameTextBox.getText();
      if (StringUtils.isEmpty(newName)) {
        errorBox.showError("User name may not be empty !");
      } else {
        User user = engine.getCurrentUser();
        user.name = newName;
        engine.saveUser(user);
      }
    }
    
    // Test code in UserFormControllerTest:
    
    void testValidUserNameChange() {
      nameTextBox = createMock(TextBox.class);
      expect(nameTextBox.getText()).andReturn("fred");
      engine = createMock(Engine.class);
      User user = createMock(user);
      user.setName("fred");
      expectLastCall();
      expect(engine.getCurrentUser()).andReturn(user);
      engine.saveUser(user);
      expectLastCall();
      replay(user, engine, nameTextBox);
    
      UserFormController controller = new UserFormController();
      controller.setNameTextBox(nameTextBox);
      controller.setEngine(engine);
      controller.changeUserNameButtonClicked();  
    
      verify(user, engine, nameTextBox);
    }
    
    void testEmptyUserNameChange() {
      nameTextBox = createMock(TextBox.class);
      errorBox = createMock(ErrorBox.class);
      expect(nameTextBox.getText()).andReturn("");
      errorBox.showError("User name may not be empty !");
      expectLastCall();
      replay(nameTextBox, errorBox);
    
      UserFormController controller = new UserFormController();
      controller.setNameTextBox(nameTextBox);
      controller.setErrorBox(errorBox);
      controller.changeUserNameButtonClicked();  
    
      verify(nameTextBox, errorBox);
    }