Rails TDD - 先写什么?

4

我刚开始学习使用Rails进行TDD。有一个问题让我感到困惑,那就是“何时编写测试”。所有的指南都建议在编写任何代码之前编写测试,但如果我创建了一个Person模型,并在编写任何代码之前编写以下测试;

p = Person.new
p.firstname = "mikey"
p.lastname = "hogarth"
assert_equal p.fullname, "mikey hogarth"

如果我没有实现“fullname”方法,测试本身将不会失败,而是会崩溃!我会收到一个运行时错误。因此,在编写代码之前,我无法使该测试失败。
TDD程序员通常如何处理这种情况?基本上是使用虚拟方法存根,还是有更好的方法?
===编辑===
提出了许多很好的想法。最终,我决定以下选项最优雅地实现了我想要做的事情;
if p.respond_to? "fullname"
  assert_equal "Mikey Hogarth", p.fullname
else
  flunk "fullname not implemented"
end

===第二次编辑===

如果你看到了这个答案,似乎我的TDD方法存在问题,所以虽然上面的代码可以工作,但并不是好的实践。


为什么不尝试测试p.fullname是否已定义? - prusswan
我认为可能是这样的,我正在尝试使用这种语法:p = Person.new; methods = %q{ name firstname fullname }; methods.each { |method| assert p.respond_to? method }. 人们通常是这样做的吗? - Mikey Hogarth
@MikeyHogarth 我建议你获取Kent Beck的《通过示例实现测试驱动开发》。虽然它使用Java,但书中的概念适用于任何语言。它被认为是TDD的“Hello World”书籍。http://www.amazon.co.uk/Test-Driven-Development-Addison-Wesley-Signature/dp/0321146530 - Finglas
3个回答

3
您希望编写您所需要的代码。在C-style语言/静态编译语言中,如您所正确说明的那样,即使是上述代码也不会编译通过,这没关系,您可以实现最基本的内容来使代码能够编译通过以便运行测试。换句话说,测试驱动设计。

我对Ruby已经很生疏了,但在上面的例子中,对于不存在的方法/属性会抛出method_missing,因此您需要创建它们。

class Person
  attr_accessor :firstname, :lastname

  def fullname

  end
end

如果你现在运行测试,你将会得到 nil 返回的 fullname。因此,我们需要实现 fullname 方法。这里需要注意的是,消息已经改变了,而不是 Ruby 抱怨缺少方法,测试正在抱怨我们没有正确地实现方法。
def fullname
   return @firstname + " " + @lastname
end

现在你的测试将会通过。

基本上,你想要改变你的测试运行后显示的信息(这将证明你正在取得进展),或者你想让它通过。测试通过后,你可以进行重构。上述方法很简单,但你可以省略return语句,使用字符串格式化或者其他方式。只要测试通过了,你就知道可以继续前进了。


我怀疑这可能需要编写虚拟方法 - 不过我会再等一会儿,看看是否有其他人能想到更好的解决方案。 - Mikey Hogarth
正如我所说,我的Ruby/Rails水平较弱,但Corey Haines有一个很棒的演讲,介绍如何将Rails混乱的代码与你的Ruby代码分离。值得一看,因为如果上述模型依赖于ActiveRecord,你可能需要稍微改变你的方法。请参见http://confreaks.net/videos/641-gogaruco2011-fast-rails-tests。 - Finglas
@buruzaemon - 发现得好。这就是为什么配对编程是有效的 ;) - Finglas

1

在这些情况下,测试代码崩溃是可以的。把它看作是一个失败。

在实际编写代码之前编写测试代码将使您有可能在实际开始实现该类之前设计被测试类的接口。您将拥有一个很好的类使用示例!

然后,您需要创建实际的被测试类并进行测试,而不是崩溃。然后使此测试通过,重构并继续编写失败的测试。


+1 挂掉的测试就是失败的测试。你做得很好。TDD 要求你先编写测试。 - Carl Manaster

0
我的方法是使用条件检查p.fullname.nil?进行测试,或者在之前加入assert_not_nil(p.fullname)作为先决条件。如果先决条件失败,将会阻止剩余的测试执行。

这仍然会导致一个NoMethod运行时崩溃,而我正试图避免这种情况。 - Mikey Hogarth
您的建议让我找对了方向 - 我已经编辑了原始帖子以展示我最终的做法。 - Mikey Hogarth
@MikeyHogarth,虽然这对你有所帮助。但这不是TDD,事实上这是一种相当可怕的方法。你不应该需要编写自定义逻辑来决定你需要断言什么。 - Finglas
好的Finglas,在进行了一些阅读之后,我要转而采用您的回答! - Mikey Hogarth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接