1

I am currently reading a book about testing with testNG and Mockito in order to refine my skills of using that tools, widen my knowledge about testing in general and improve the quality of code I create.

After the 4th chapter, "Test Driven Development" there are plenty of exercises I should do to harden what I have just learnt about TDD and to get a habit of the cycle Red Test -> Implement code -> Green Test -> Refactor. One of them is called "PasswordValidator", which should, well, validate strength of a password.

One of recommended approaches to such development is to start with the easiest possible test and incrementally, by writing more complicated test cases, implement more complex functionality. Truth be told, normally I would solve that problem by writing a proper regex ( like for instance this one: https://stackoverflow.com/a/5142164/2576122), write a few tests that will cover whatever border cases I might imagine and be done with that.

And here, I go with firstly creating PasswordValidatorTest class and in it tests such as:

@Test
public void returnsTrueWhenPasswordIsAtLeastEightCharsLong() {
    assertTrue(atLeastEightCharactersLong(EIGHT_CHARACTERS_LONG_PASSWORD));
}

I check that it fails, implement logic in PasswordValidator:

public static boolean atLeastEightCharactersLong(String passwordToValidate) {
    if (passwordToValidate.length() >= 8) {
        return true;
    } else {
        return false;
    }
}

I check that it is green, then I refactor:

public static boolean atLeastEightCharactersLong(String passwordToValidate) {
    return (passwordToValidate.length() >= 8);
} 

The same goes with tests for containing digits, special signs etc. At some point of such procedure, I end up with a final test:

@Test
public void shouldReturnTrueIfPasswordIsValid() {
    assertTrue(PasswordValidator.isValid(VALID_PASSWORD_EXAMPLE));
}

For which implementation is:

public static boolean isValid(String passwordToValidate) {
    return atLeastEightCharactersLong(passwordToValidate) && containsAtLeastTwoDigits(passwordToValidate) &&
            containsAtLeastOneSpecialSign(passwordToValidate);
}

And then after tests is green I can clearly see, that all earlier tested methods should be, from the very beginning, private ones.

Does it often happen, and is it accepted, that during coding some tests in TDD, that were at first useful, are removed? Because one thing for certain - these methods should not be exposed at all to that class user, so answer such as "make them protected" don't appeal to me.

Or maybe my approach from the very beginning is invalid and I should have only written a test for isValid() method, to test behaviour of an API? If I did so such test would be too general though and wouldn't allow me to check all border cases while development, or maybe I got it all wrong? Is there a line between what to test and what should not be tested at all? What should be the granularity?

Community
  • 1
  • 1
Mateusz Chrzaszcz
  • 1,240
  • 14
  • 32
  • IMO, your first test should test the only public method that the validator should have (`isValid()`) and be `isValidShouldReturnFalseIfPasswordIsLessThan8CharactersLong()`. Your first implementation of `isValid()` (`return true;`) will fail this test, until you actually implement the length validation. – JB Nizet Aug 24 '14 at 16:19

3 Answers3

3

Or maybe my approach from the very beginning is invalid and I should have only written a test for isValid() method

Yes, this.

So you write something like this as a starting point:

public static boolean isValid(String passwordToValidate) {
    return false;
}

Then you can write your first test:

@Test
public void returnsTrueWhenPasswordIsAtLeastEightCharsLong() {
    assertTrue(isValid(EIGHT_CHARACTERS_LONG_PASSWORD));
}

Which will fail: RED.

So you make the simplest change to the code under test to get it to pass:

public static boolean isValid(String passwordToValidate) {
    return true;
}

Then the test will pass: GREEN.

But you haven't fully tested the 8-character limit yet:

@Test
public void returnsFalseWhenPasswordIsLessThanEightCharsLong() {
    assertFalse(isValid(SEVEN_CHARACTERS_LONG_PASSWORD));
}

This will fail -- RED -- because the method under test always returns true now.

So you make the smallest change to make all the tests pass:

public static boolean isValid(String passwordToValidate) {
    return passwordToValidate.length() >= 8;
}

And you're GREEN again.

And you proceed like this, adding other tests and each time changing the code you're testing as little as possible to get the tests to pass.

You might find it useful to refactor your isValid method at some stage by splitting it up into some private methods that you call. That's fine: it's part of the refactor step, and your tests should still pass since the public interface hasn't changed.

Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
Matthew Strawbridge
  • 19,940
  • 10
  • 72
  • 93
0

The key point here is that you are missing the main concept that is about creating the abstraction that represents the password and it gives you the starting point. Because of that missing entity you lost the focus and wrote the test cases that way. You want to represent a Password so you should have a class that models that entity, in terms of domain driven design it's a value object. A password once created it actually represents the concept it models so I would only have TWO tests and would add incrementally the check guards in the constructor

testCreatesInvalidPassword() {
// catch exception
Password.fromString("asda");
Password.fromString("asdas1");
Password.fromString("asdas1...");
}

testCreatesValidPassword() {
Password.fromString("asdasdsad");
Password.fromString("asdas123");
Password.fromString("asdas123");
}
0

One piece of the Red -> Green -> Refactor loop that you may be overlooking is the Refactor step. This step should also apply to your Tests. So If you get to a point where you have duplication between tests, you should refactor those out and if that means deleting tests then you should feel confident that that test was no longer needed.

TDDdev
  • 1,490
  • 1
  • 17
  • 18