Java 测试驱动开发 - 如何在不存在实现代码之前编写测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20768738/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Test-Driven Development - How to write a test before none of implementation code exists
提问by phanin
I'm learning TDD but struggling to adopt it as it's not straightforward.
我正在学习 TDD,但很难采用它,因为它并不简单。
The question I cannot answer is "How to write a test before any of implementation code exists?".
我无法回答的问题是“如何在任何实现代码存在之前编写测试?”。
If our target class / target method / target parameter type / target return type don't exist,
如果我们的目标类/目标方法/目标参数类型/目标返回类型不存在,
- What do we refer to while writing code in the test. How do we start writing the test?
- How'd the test fail if all we could write is just the test method name before the actual implementation code?
- 在测试中编写代码时我们指的是什么。我们如何开始编写测试?
- 如果我们能写的只是实际实现代码之前的测试方法名称,测试怎么会失败?
Everybody tells WHY but not HOW
每个人都说为什么但不说如何
I've tried my best to find resources that elaborate on writing tests before production code but, assuming that I missed good resources, most of them are filled with cliches explaining why TTD is important than focusing on practises to adopt it.
我已经尽力寻找在生产代码之前详细说明编写测试的资源,但是,假设我错过了好的资源,它们中的大多数都充满了陈词滥调,解释了为什么 TTD 比专注于采用它的实践更重要。
An example use-case.
一个示例用例。
Let's assume that we are developing a software for a University and our use-case is course registration.
假设我们正在为一所大学开发软件,我们的用例是课程注册。
To keep it simple, let us confine this discussion to
为简单起见,让我们将讨论限制在
- scenario : "A Student can enroll in a maximum of any 3 courses per semester"
- testing service layer and dao layer.
- 场景:“一个学生每学期最多可以注册任何 3 门课程”
- 测试服务层和dao层。
Pseudocode
伪代码
ENROLL(studentId, courseId)
//check if student enrolled in less than 3 courses in the same semester as given courseId belongs in.
//if yes, enroll him/her.
//if not, return an error.
The actual implementation of above could span a couple of classes involving services, daos, etc.
上面的实际实现可以跨越几个涉及服务、daos 等的类。
Please could you explain how to test-driven-develop it step by step? If you were to implement this using TDD, how'd you do it step-by-step.
请你能解释一下如何一步一步地进行测试驱动开发吗?如果您要使用 TDD 来实现这一点,那么您是如何一步一步地做到这一点的。
I am hoping that this could aid many struggles like me in the future.
我希望这可以在未来帮助像我这样的许多斗争。
采纳答案by Evgeniy Dorofeev
Create EnrollingServiceTest class in src/test/java in the same package as EnrollingService
在与 EnrollingService 相同的包中的 src/test/java 中创建 EnrollingServiceTest 类
class EnrollingServiceTest {
private EnrollingService enrollingService;
@Before
public void init() {
enrollingService = new EnrollingService();
}
@Test
public void testEnroll() {
boolean result = enrollingService.enroll(1l, 1l);
assertTrue(result);
...
IDE (I am assuming you are using IDE) shows errors - EnrollingService does not exists .
IDE(我假设您正在使用 IDE)显示错误 - EnrollingService does not exist 。
Point cursor on EnrollService - IDE will offer to create a class - let it create in src/main/java
将光标指向 EnrollService - IDE 将提供创建一个类 - 让它在 src/main/java 中创建
Now IDE says that enroll(long, long) method is missing - let IDE create it for you.
现在 IDE 说缺少 enroll(long, long) 方法 - 让 IDE 为您创建它。
Now IDE shows no errors. Run the test - it fails. Go to enroll and start implementing the logic
现在 IDE 没有显示任何错误。运行测试 - 它失败了。去报名开始实现逻辑
And so on...
等等...
回答by Carl Manaster
public void shouldNotEnrollInMoreThanFourClassesInASemester() {
Enroller enroller = new Enroller();
Student student = new Student();
Semester one = new Semester();
Semester two = new Semester();
Course geology = new Course(one);
Course architecture = new Course(one);
Course calculus = new Course(one);
Course sociology = new Course(one);
Course geometry = new Course(two);
assertOk(enroller.enroll(student, geology));
assertOk(enroller.enroll(student, architecture));
assertOk(enroller.enroll(student, calculus));
assertNotOk(enroller.enroll(student, sociology));
assertOk(enroller.enroll(student, geometry));
}
回答by James Black
In your scenario you should test each layer separately, so mock out the dao when testing the service layer.
在您的场景中,您应该单独测试每一层,因此在测试服务层时模拟 dao。
When you first write the test it won't compile, which means it fails, but that is fine as the classes don't exist.
当您第一次编写测试时,它不会编译,这意味着它会失败,但这很好,因为这些类不存在。
In your example which layer should enforce to register in at most 3 courses? That will affect how you test.
在您的示例中,哪一层应该强制注册最多 3 门课程?这将影响您的测试方式。
Writing the test first will help you work through these types of questions.
首先编写测试将帮助您解决这些类型的问题。
As was mentioned this is too open-ended for a definitive answer but if you start writing your test then post as an update it could help.
如前所述,这对于一个明确的答案来说过于开放,但是如果您开始编写测试,然后将其作为更新发布,它可能会有所帮助。
So, write your dao test then write the classes and methods so it compiles but it should still fail until yiu finish the implementation. You will probably want to test for 2,3,4 class registrations and ensure each fails appropriately, then?finish the implementation.
因此,编写您的 dao 测试,然后编写类和方法,以便它进行编译,但在 yiu 完成实现之前它仍然应该失败。您可能想要测试 2,3,4 个类注册并确保每个都正确失败,然后完成实现。
回答by Vincent Ramdhanie
This will become clearer when you focus on the expected behavior of the code rather than the implementation of the code. So given the scenario you outlined you may arrive at the conclusion that you will have to write the enroll() method in some class. You can then consider how you are going to test this class.
当您关注代码的预期行为而不是代码的实现时,这将变得更加清晰。因此,鉴于您概述的场景,您可能会得出结论,您必须在某个类中编写 enroll() 方法。然后,您可以考虑如何测试这个类。
You begin by considering the conditions of the class and what is expected of it. Maybe you can identify certain invariants of the class. In that case to test the class you consider the ways in which that invariant can be violated.
您首先要考虑班级的条件以及对班级的期望。也许您可以识别该类的某些不变量。在这种情况下,为了测试类,您需要考虑违反该不变量的方式。
So taking the statement: a student may register for a maximum of 3 courses per semester, you consider the ways that this can occur.
因此,请考虑以下声明:学生每学期最多可以注册 3 门课程,您需要考虑可能发生的情况。
- Student is registered for 0 courses for the given semester, attempt to register for a course, result: registration successful; student is now registered for 1 course for the given semester.
- Student is registered for 1 course for given semester, attempt to register for a course, result: registration successful; student is now registered for 2 courses for the given semester.
- Student is registered for 3 courses for the given semester, attempt to register for a course, result: Fail (maybe exception is thrown?)
- etc etc
- 学生在指定学期注册了0门课程,尝试注册课程,结果:注册成功;学生现在注册了给定学期的 1 门课程。
- 学生在指定学期注册了1门课程,尝试注册课程,结果:注册成功;学生现在注册了给定学期的 2 门课程。
- 学生在指定学期注册了 3 门课程,尝试注册课程,结果:失败(可能抛出异常?)
- 等等等等
Next you actually write these tests. Each of these can be a test method. So the test method would ensure that the objects are created and the environment is set up as expected. Then call the method and compare the result to the expected result. If what you expect to happen actually does happen then the test passed.
接下来,您实际编写这些测试。这些中的每一个都可以是一种测试方法。因此,测试方法将确保创建对象并按预期设置环境。然后调用该方法并将结果与预期结果进行比较。如果您期望发生的事情确实发生了,那么测试就通过了。
Now, initially, since you did not as yet write the method the tests would not actually pass. But as you start to write code your tests would start to pass and eventually 100% of your tests will pass at which point you are satisfied that your code meets the requirements.
现在,最初,由于您尚未编写该方法,因此测试实际上不会通过。但是当您开始编写代码时,您的测试将开始通过,最终 100% 的测试将通过,此时您对代码满足要求感到满意。