HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Am I implementing BDD correctly?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
bddimplementingcorrectly

Problem

I'm writing a utility that validates fields. I decided to try my hand at Behaviour Driven Development (BDD). The validator utilises "rules" to determine if field is valid.

Three different types of rules exist:

  • RegexRule A field conforms to specific pattern(s)



  • RequireRule A value is present



  • EqualityRule A field's value equals another's value



A rule is tested against a value. If the value does not conform to the rule, a flag is set which marks the rule as erroneous. Calling getError() on an erroneous rule returns a string that details the error. Furthermore, the rule is reset (no longer erroneous).

Is this the correct manner of implementing BDD on this class? I'd greatly appreciate advice on how I could improve on this.

```
describe('RequireRule', function() {
var nameErr = 'The name field is required';

beforeEach(function() {
this.r = new RequireRule('name', nameErr);
});

describe('test()', function() {
it('should return false when value provided is null or undefined', function() {
expect(this.r.test('asd')).to.equal(true);
});
it('should return true when value provided is not null or undefined', function() {
expect(this.r.test(null)).to.equal(false);
});
});

describe('isErroneous()', function() {
it('should become erroneous when value tested is null or undefined', function() {
r.test(null);
expect(this.r.isErroneous()).to.equal(true);
});
it('should not be erroneous when value tested is not null or undefined', function() {
r.test('asd');
expect(this.r.isErroneous()).to.equal(false);
});
});

describe('getError()', function() {
it('should return null when the test() succeeded', function() {
r.test('asd');
expect(this.r.getError()).to.equal(null);
});
it('should return the error string when the test() failed, and s

Solution

This is roughly what BDD should look like. You're definitely on the right track in that you have a specific set of postconditions and expected results for every "public" method. However, the tests for these postconditions are incomplete in some respects that I think will probably come up as edge conditions and/or browser quirks (assuming your system is going to run in a browser). In addition, you're testing behaviors in this suite that are shared between your subclasses - even if they don't actually share a prototype, they do have a common interface and common expectations.

For incompleteness, I'm referring to 'should return false when value provided is null or undefined' when the corresponding test only tests null. This should be two tests, one for null and another for undefined. In addition, you should define and test the function's behavior for other values that are commonly mistaken for null and undefined, like 0, false, and ''. The empty string is of particular importance because it's unclear from your tests whether an empty string is acceptable as long as it's specified - the source of your data might determine whether that's applicable. I would go with two or three tests per native type: one that is acceptable, one that is not (if both are applicable), and one that is falsy (if the falsy value for the type is not already covered). If you really want to go all out, define behavior for the parameters '0', 'false', 'undefined', and NaN.

This sounds like a pain to do - especially when you see that you need to define said behavior for all of the other functions whose preconditions are the same. The root of the problem here is that your preconditions are not very DRY. Each function has two test cases: null and 'asd' as inputs to test. Since null and 'asd' are not really the only two inputs you can expect to get, your tests shouldn't be coupled to those two inputs. Further, when you go to write your tests for your other rule subclasses, you will be unable to re-use your isErroneous and getError tests because they have specifically named null as erroneous and 'asd' as acceptable.

I'm not a mocha expert, so I don't know how helpful or cooperative it will be, but you can refactor your suites to test states rather than classes, and it will be far DRYer. From what I can tell, the rule subclasses all have the following features:

  • A (theoretically unbounded) set of inputs to the test method that will:



  • return false



  • leave the object in an erroneous state



  • A (theoretically unbounded) set of inputs to the test method that will:



  • return true



  • leave the object in a validated state



  • An initial state before test is ever called, whose behavior remains unspecified.



Each of your subclasses has a different set of erroneous and valid inputs, but you can easily define the behavior of those three states independently:

  • A rule object in an erroneous state should:



  • return true from isErroneous()



  • return non-null from getError()



  • (become | not become) valid when a valid input is provided to test



  • A rule object in a validated state should:



  • return false from isErroneous()



  • return null from getError()



  • (become | not become) erroneous when an invalid input is provided to test



  • A rule object in its initial state should:



  • probably have defined behavior for getError and isErroneous, too.



  • become valid when a valid input is provided to test



  • become erroneous when an invalid input is provided to test



Now, your tests for all of your classes are reduced to the task of generating the input test cases and expected states they will generate. For the RequiredRule, that could be easily expressed in two arrays; you might want something different for your more complicated rules, since the criteria are presumably individualized per object for those subclasses. I might go with something like a generator.

The concept of a class is helpful for BDD when you have specific class invariants, but with javascript you are by no means limited to a one-to-one correspondence between a class (constructor/prototype) and its defined behaviors. Like anything else in software design, you can tell your unit test is wrong as soon as you type the same thing 2 or 3 times. Usually, you can fix the repetition by making the object of your suite (parameter to describe) something more applicable than the name of the constructor.

Context

StackExchange Code Review Q#39576, answer score: 5

Revisions (0)

No revisions yet.