改进此代码的方法

发布于 2024-09-24 00:59:18 字数 907 浏览 3 评论 0原文

我正在尝试使用 Scalatest 为我的 java 应用程序编写一些测试代码。我想,由于 Scala 具有更易读的语法,因此会产生更易读的测试代码。

到目前为止,这就是我所做的:

package com.xyz

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import com.xyz.SecurityService
import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.mockito.Matchers._
import javax.servlet.jsp.tagext.Tag

class CheckRoleTagSpec extends FlatSpec with ShouldMatchers with  MockitoSugar {

  behavior of "CheckRole tag"

  it should "allow access when neither role nor root defined" in {
    val securityServiceMock = mock[SecurityService]

    val tag = new CheckRoleTag()
    tag.setSecurityService(securityServiceMock)

    tag.setGroup("group")
    tag.setPortal("portal")

    tag.setRoot(false)
    tag.setRole(null)

    tag.doStartTag should be(Tag.SKIP_BODY)
  }

}

我对这段代码非常失望。这实际上与我需要用 Java 编写的东西是一样的。请帮助我使它更像 scala 和功能。

I am trying to write some test code for my java application using Scalatest. I figured, since Scala has so much more readable syntax it would result with more readable test code.

So far, this is what I managed:

package com.xyz

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import com.xyz.SecurityService
import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.mockito.Matchers._
import javax.servlet.jsp.tagext.Tag

class CheckRoleTagSpec extends FlatSpec with ShouldMatchers with  MockitoSugar {

  behavior of "CheckRole tag"

  it should "allow access when neither role nor root defined" in {
    val securityServiceMock = mock[SecurityService]

    val tag = new CheckRoleTag()
    tag.setSecurityService(securityServiceMock)

    tag.setGroup("group")
    tag.setPortal("portal")

    tag.setRoot(false)
    tag.setRole(null)

    tag.doStartTag should be(Tag.SKIP_BODY)
  }

}

I am quite dissapointed with this code. It's practically the same thing I would need to write in Java. Please help me make it more scala-like and functional.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

淑女气质 2024-10-01 00:59:18

您无法通过重写测试来修复丑陋的测试。您只能通过重新设计正在测试的 API 来修复它。

嗯,从技术上来说,如果你真的很努力,非常邪恶,非常愚蠢,喝醉了或者非常累,那么有可能为好的 API 编写丑陋的测试。但是编写一个丑陋的测试需要努力,而且程序员很懒,所以不太可能有人会选择编写一个丑陋的测试。编写丑陋的测试几乎是不可能的:你插入一些东西,取出一些东西,检查你是否得到了你所期望的东西。就是这样。那里确实没有什么可丑化的。

测试只是使用 API,就像 API 用户使用它一样。它基本上是如何正确使用 API 的示例,而且几乎作为副作用,它碰巧检查 API 实际上是否正确实现。这就是为什么丑陋的测试是糟糕的 API 设计的良好指标,也是为什么测试驱动 API 设计是一件好事,即使您不进行 TDD。

在这种特殊情况下,我可以看到很多改进 API 的方法,尽管这些建议必然是不完整、肤浅和简单化的(更不用说可能是错误的),因为我对您的域一无所知:

  • 更好名称setRoot听起来像是在设置根。但是,除非 false 是层次结构的根,否则我假设它的实际上设置是该标签是否是根。因此,它应该被命名为 isRootmakeRootsetIsRoot 或类似的名称。
  • 更好的默认值:继续使用setRoot,假设我的猜测是正确的并且这设置了标签是否是根,那么默认值是错误的。根据“根”概念的定义,只能有一个根。因此,您强制用户setRoot(false)每次指定,除了他们实际定义根的一次 。非根标签应该是默认值,并且您应该只强制为实际上是根的一个标签setRoot(true)
  • 更好的默认值,第二部分setRole(null)。严重地?您是否强制用户明确设置取消设置的角色?为什么不简单地取消设置默认值呢?毕竟,测试被称为“...当角色和根都没有定义时”,那么为什么要定义它们呢?
  • Fluent API / Builder Pattern:如果您确实必须构造无效对象(但请参阅下一点),至少使用 Fluent API 或 Builder Pattern 之类的东西。
  • 仅构造有效对象:但实际上,对象在构造时应该始终有效、完整且完全配置。您不必先构造一个对象,然后再配置它。

这样,测试基本上就变成了:

package com.xyz

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import com.xyz.SecurityService
import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.mockito.Matchers._
import javax.servlet.jsp.tagext.Tag

class CheckRoleTagSpec extends FlatSpec with ShouldMatchers with MockitoSugar {
  behavior of "CheckRole tag"
  it should "allow access when neither role nor root defined" in {
    val tag = new CheckRoleTag(mock[SecurityService], "group", "portal")

    tag.doStartTag should be(Tag.SKIP_BODY)
  }
}

You cannot fix an ugly test by rewriting the test. You can only fix it by redesigning the API that is being tested.

Well, technically, it is possible to write ugly tests for good APIs if you try really hard, are very evil, very, stupid, very drunk or very tired. But writing an ugly test takes effort and programmers are lazy, so it is very unlikely that someone would write an ugly test by choice. It is almost impossible to write ugly tests: you stick something in, you get something out, you check whether you got what you expected. That's it. There's really nothing to uglify there.

A test just uses the API the same way a user of the API would use it. It's basically an example of how to use the API properly, that also, almost as a side-effect, happens to check that the API is actually implemented properly. That's why an ugly test is a good indicator for bad API design, and it's also why test-driving the API design is a good thing, even if you don't do TDD.

In this particular case, I can see quite a few ways to improve the API, although these suggestions are necessarily incomplete, shallow and simplistic (not to mention probably wrong), since I don't know anything about your domain:

  • Better Names: setRoot sounds like it is setting the root. But, unless false is the root of your hierarchy, I assume that what it is actually setting is whether or not this Tag is the root. So, it should rather be named isRoot or makeRoot or setIsRoot or something like that.
  • Better Defaults: continuing with setRoot, assuming that my guess is correct and this sets whether or not the Tag is the root, then the default is the wrong way around. By the very definition of the concept of "root", there can only ever be one root. So, you are forcing your users to specify setRoot(false) every single time, except for that one time when they are actually defining a root. Non-root Tags should be the default, and you should only be forced to setRoot(true) for that one Tag which actually is the root.
  • Better Defaults, Part II: setRole(null). Seriously? You are forcing your users to explicitly set the role to be unset? Why not simply make unset the default? After all, the test is called "... when neither role nor root defined", so why define them?
  • Fluent API / Builder Pattern: if you really have to construct invalid objects (but see next point), at least use something like a Fluent API or a Builder Pattern.
  • Only Construct Valid Objects: But really, objects should always be valid, complete and fully configured, when constructed. You shouldn't have to construct an object, and then configure it.

That way, the test basically becomes:

package com.xyz

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import com.xyz.SecurityService
import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.mockito.Matchers._
import javax.servlet.jsp.tagext.Tag

class CheckRoleTagSpec extends FlatSpec with ShouldMatchers with MockitoSugar {
  behavior of "CheckRole tag"
  it should "allow access when neither role nor root defined" in {
    val tag = new CheckRoleTag(mock[SecurityService], "group", "portal")

    tag.doStartTag should be(Tag.SKIP_BODY)
  }
}
浮生未歇 2024-10-01 00:59:18

下面的代码创建了新的匿名类,但 doStartTag 按预期返回结果:

...
(new CheckRoleTag{
   setSecurityService(mock[SecurityService])
   setGroup("group")
   setPortal("portal")
   setRoot(false)
   setRole(null)
} doStartTag) should be(Tag.SKIP_BODY)
...

The code below creates new anonymous class, but doStartTag returns result as expected:

...
(new CheckRoleTag{
   setSecurityService(mock[SecurityService])
   setGroup("group")
   setPortal("portal")
   setRoot(false)
   setRole(null)
} doStartTag) should be(Tag.SKIP_BODY)
...
平定天下 2024-10-01 00:59:18

由于这个特定的测试只是在 Java 中实现的对象上调用一堆 setter,因此您无法做太多事情来使其更加简洁、功能性或 scalaish。您可以删除一些重复内容,例如

it should "allow access when neither role nor root defined" in {
  val securityServiceMock = mock[SecurityService]

  val tag = new CheckRoleTag()

  locally { 
    import tag._
    setSecurityService(securityServiceMock)
    setGroup("group")
    setPortal("portal")
    setRoot(false)
    setRole(null)
  }

  tag.doStartTag should be(Tag.SKIP_BODY)
}

“我不确定在这种情况下是否真的值得”。

Since this particular test is just calling a bunch of setters on a object implemented in java, there's not a whole lot you can do to make it more succinct or functional or scalaish. You could remove some repetition with something like

it should "allow access when neither role nor root defined" in {
  val securityServiceMock = mock[SecurityService]

  val tag = new CheckRoleTag()

  locally { 
    import tag._
    setSecurityService(securityServiceMock)
    setGroup("group")
    setPortal("portal")
    setRoot(false)
    setRole(null)
  }

  tag.doStartTag should be(Tag.SKIP_BODY)
}

I'm not sure whether it's really worth it in this case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文