51

I'm learning TDD but struggling to adopt it as it's not straightforward.

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.

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.

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.

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.

I am hoping that this could aid many struggles like me in the future.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
phanin
  • 5,327
  • 5
  • 32
  • 50
  • You do know this question is kind of open ended and would have different answers right? – Emmanuel John Dec 25 '13 at 03:28
  • 1
    You'll need to sit down and read a book (or at least watch some videos). I recommend Test Driven Development: By Example by Kent Beck. – TrueWill Dec 25 '13 at 03:34
  • Thanks for the replies. Yep, I realize that this is an open-ended question. Just wanted to see how people tackle the same and might help me and many people to get on track. – phanin Dec 25 '13 at 04:08
  • This is a great question.Even I have it since long time. Because of this HOW many developers go away without TDD. – swapyonubuntu Jan 18 '16 at 16:34

4 Answers4

44

Create EnrollingServiceTest class in src/test/java in the same package as EnrollingService

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 .

Point cursor on EnrollService - IDE will offer to create a class - let it create in src/main/java

Now IDE says that enroll(long, long) method is missing - let IDE create it for you.

Now IDE shows no errors. Run the test - it fails. Go to enroll and start implementing the logic

And so on...

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • 7
    Now I finally understand that a 'failing test' also means failing due to compilation errors. Superb! Thank you very much. Your reply did shed light on actual sequence of steps in TDD. – phanin Jan 04 '14 at 21:07
  • 2
    @phani, I think its not only about failing due to compilation errors. its about errors occurring due to test case failed as well. – Shekhar Jul 04 '15 at 04:59
  • Or if you wish to use a more self-describing approach you could try [Ginkgo4j](http://bit.ly.ginkgo4j) and you would end up with something that looks more like [this](http://ideone.com/e.js/4rnEgi) – Paul Warren Mar 18 '17 at 07:13
4

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.

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.

  1. 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.
  2. 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.
  3. Student is registered for 3 courses for the given semester, attempt to register for a course, result: Fail (maybe exception is thrown?)
  4. etc etc

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.

Vincent Ramdhanie
  • 102,349
  • 23
  • 137
  • 192
3
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));
}
Carl Manaster
  • 39,912
  • 17
  • 102
  • 155
  • Your test may be fine for the service layer but not the dao, I believe, as you are testing based on different tables, – James Black Dec 25 '13 at 03:44
1

In your scenario you should test each layer separately, so mock out the dao when testing the service layer.

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.

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.

James Black
  • 41,583
  • 10
  • 86
  • 166
  • +1, Thanks for the reply even though my question was vague and it was harder to come up with a definitive answer. – phanin Jan 04 '14 at 21:13