One assertion per test (zz)

Source: Internet
Author: User

 

ZZ from: http://www.artima.com/weblogs/viewpost.jsp? Thread = 1, 35578

See another ZZ blog: One expectation per test

Shift your paradigm!
One assertion per test
By Dave astels
February 23,200 4

Summary
For some time I 've been thinking about how TDD tests can be as simple, as expressive, and as elegant as possible. this article attributes es a bit about what it's like to make tests as simple and decomposed as possible: aiming for a single assertion in each test.

A while ago there was a bit of fuss on the testdrivendevelopment Yahoo group about the idea of limiting yourself to one assertion per test method, which is a guideline that others and I offer for TDD work.

An address parser was the example of a situation where it was argued that multiple assertions per test made sense. date was formatted with one address per line, each in one of the following formats:

    1. Addr1 $ addr2 $ CSP $ country
    2. Addr1 $ addr2 $ CSP
    3. Addr1 $ CSP $ country
    4. Addr1 $ CSP

The poster went on to say:My first inclination is/was to write a test like this:

 
A = Createobject ("Address"). getaddressparts ("addr1 $ addr2 $ city il 60563 $ country") assertequals ("addr1",. addr1) assertequals ("addr2",. addr2) assertequals ("city il 60563",. citystatepostalcd) assertequals ("country",. country)

They didn't see how to achieve this with one assertion per test, as there are obviously four things to test in this case. I decided that rather than simply reply, I wocould write some tests and Code to define strate my view on the matter, and offer a solid response.

For this problem, I chose squeak Smalltalk (see www.squeak.org) and Java. For the sake of conciseness, I'll omit any required accessors.

So, where to start? Well, when doing TDD it often makes sense to start with something simple to quickly and easily get some code written and working. then it can be extended and evolved in response to further test driving. here the simplest case is:Addr1 $ CSP. There are two requirements in the parsing of this example: thatAddr1Was recognized, and thatCSPWas recognized. Viewed this way, we need two tests. We start with oneAddr1:

Squeak:

Testaddr1 | anaddress: = address from: 'addr1 $ city il 60563 '. Self assert: anaddress addr1 equals: 'addr1'

Java:

 
Public void testaddr1 () throws exception {address anaddress = new address ("addr1 $ city il 60563"); assertequals ("addr1", anaddress. getaddr1 ());}

To get this to pass we needAddressClass andFrom:Factory method, which creates an instance and has it parse the address string. For brespon, I'll skip the "Return literal" step.

Squeak:

Object subclass: # addressinstancevariablenames: 'addr1' classvariablenames: ''oldictionaries: ''category: 'adaption-one assertion test' (Class Method) from: astring ^ self new parse: astring; yourselfparse: astring | parts: = astring findtokens: '$ '. addr1: = (parts at: 1)

Java:

 
Public Class address {private string addr1; Public Address (string astring) {parse (astring);} private void parse (string astring) {stringtokenizer parts = new stringtokenizer (astring, "$"); addr1 = parts. nexttoken ();}}

That's well & good. The next test isCSP.

Squeak:

 
Testcsp | anaddress: = address from: 'addr1 $ city il 100'. Self assert: anaddress CSP equals: 'city il 100'

Java:

 
Public void testcsp () throws exception {address anaddress = new address ("addr1 $ city il 60563"); assertequals ("city il 60563", anaddress. getcsp ());}

Address> parse:Will need to be extended (and we need to addCSPInstance variable and accessors ):

Squeak:

 
Parse: astring | parts: = astring findtokens: '$'. addr1: = (parts at: 1). CSP: = (parts at: 2)

Java:

 
Private void parse (string astring) {stringtokenizer parts = new stringtokenizer (astring, "$"); addr1 = parts. nexttoken (); CSP = parts. nexttoken ();}

So. we have two tests for this one situation. notice the duplication in the tests... the creation of the instance of address that is being probed. this is the fixture. after refactoring, we have:

Squeak:

 
Testcase subclass: # addr1csptestsinstancevariablenames: 'anaddress' classvariablenames: ''pooldictionaries:'' category: 'adaption-one assertion test '! Setupanaddress: = address from: 'addr1 $ city il 100' testaddr1self assert: anaddress addr1 equals: 'addr1' testcspself assert: anaddress CSP equals: 'city il 100'

Java:

Public class addr1csptests extends testcase {private address anaddress; protected void setup () throws exception {anaddress = new address ("addr1 $ city il 60563");} public void testaddr1 () throws exception {assertequals ("addr1", anaddress. getaddr1 ();} public void testcsp () throws exception {assertequals ("city il 60563", anaddress. getcsp ());}}

So, a fixture that creates the address instance from the string, and very simple tests that focus on each aspect of that fixture.

The next simplest case is the obvious choice for the next fixture:

Squeak:

 
Setupanaddress: = address from: 'addr1 $ city il 60563 $ country'

Java:

 
Protected void setup () throws exception {anaddress = new address ("addr1 $ city il 60563 $ country ");}

This set of tests will include onesAddr1AndCSPAs before (refactoring this to remove that duplication is left to the reader) as well as a new test for country:

Squeak:

 
Testcountryself assert: anaddress country equals: 'country'

Java:

 
Public void testcountry () throws exception {assertequals ("country", anaddress. getcountry ());}

As before, an instance variable and associated accessors need to be added toAddressClass.

This drivesAddress> parse:To evolve:

Squeak:

Parse: astring | parts: = astring findtokens: '$ '. addr1: = (parts at: 1 ). CSP: = (parts at: 2 ). country: = (parts at: 3 ifabsent: [''])

Java:

 
Private void parse (string astring) {stringtokenizer parts = new stringtokenizer (astring, "$"); addr1 = parts. nexttoken (); CSP = parts. nexttoken (); Country = parts. hasmoretokens ()? Parts. nexttoken ():"";}

From here on, the evolution gets a bit more complex, as we addAddr2Option to the mix.

Conclusion

So we took a situation that was thought to require multiple assertions in a test and did it in such as way as to have only one assertion per test.

The key is that instead of using a singleTestcaseSubclass with a complex (I. e. Multiple assertion) tests for each situation, we made each of those situations into a separate fixture. Each fixture is implemented by a separate subclassTestcase. Now each test focuses on a very small, specific aspect of that Fig.

I'm convinced writing tests like this is a useful approach. one advantage is that the resulting tests simpler and easier to understand. just as important, and maybe more so, is that by adding the specification of the behavior one tiny piece at a time, you drive toward evolving the code in small, controllable, understandable steps.

It also fits better into the test fixture centered approach that is the recommended way to organize your tests. we set up the object to test in the setup method, and tested each aspect of it in individual tests methods.

As I 've been writing this, something clicked. I see theseTestMethods as specifications of tiny facets of the required behavior. thus, it makes sense to me to be as gradual as possible about it, driving the evolution of the Code in the smallest steps possible. striving for one assertion per test is a way to do that.

If, however, you viewTestMethods as strictly implements Ming verification, then I can see how it might be seen to make sense to invoke some code and then test all the postconditions. but this view is not TDD, and doesn't buy you all of the benefits of TDD. I contend that central to TDD is this notion of working in the smallest steps possible, both for the finest-grained long-term verification, and for the most flexible design evolution. furthermore, this is best done by striving to keep tests as small, focused and simple as possible. aiming for one assertion per test is one way to get there.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.