Re: Feature request: @Assumes
David Saff <[email protected]> Wed, 14 Sep 2011 13:00:55 -0400
| Newsgroups | gmane.comp.java.junit.devel |
|---|---|
| Message-ID | <CACj8BuN-6PnD7vCVNMoKBLLqAoaZmgRRuGrD+K0nLT3xNWhdNg@mail.gmail.com> |
I'll respond at [email protected]... On Wed, Sep 14, 2011 at 5:18 AM, Stephen Connolly <[email protected]> wrote: > Consider the case where you are testing a List class... > > we have > > public class ListTest { > > =A0@Test > =A0public void newListIsEmpty() { > =A0 =A0assertThat(new List().isEmpty(), is(true); > =A0} > > =A0@Test > =A0public void newListHasSizeZero() { > =A0 =A0assertThat(new List().size(), is(0)); > =A0} > > =A0@Test > =A0public void addPutsAnElementIntoAnEmptyList() { > =A0 =A0List l =3D new List(); > =A0 =A0l.add(new Object()); > =A0 =A0assertThat(l.isEmpty(), is(false)); > =A0} > > =A0@Test > =A0public void addIncreasesSizeOfPopulatedListByOne() { > =A0 =A0List l =3D new List(); > =A0 =A0l.add(new Object()); > =A0 =A0int s =3D l.size(); > =A0 =A0l.add(new Object()); > =A0 =A0assertThat(l.size(), is(s + 1)); > =A0} > > } > > We now want to add some tests of the delete functionality... but the > reality is that until/unless some of the preceding tests are passing, > the tests for delete are meaningless. We could have a perfectly > functional List.delete() method but until such time as the above tests > are passing, there is no way to tell that the method does not work. > > Now I could code my tests like such > > =A0@Test > =A0public void deleteIsANoOpOnEmptyList() { > =A0 =A0List l =3D new List(); > =A0 =A0assumeThat(l.isEmpty(), is(true)); > =A0 =A0l.delete(new Object()); > =A0} > > But all that I am doing is repeating code from the preceding tests, > having changed all those tests' assertThat(...)s into assumeThat(...)s > > That does not seem agile to me, copy & paste & search & replace... ban > code smell there > > I would much rather be able to annotate the tests with an @Assumes > annotation that indicates that the test assumes that the specified > tests are passing, e.g. > > =A0@Test > =A0@Assumes("newListIsEmpty") > =A0public void deleteIsANoOpOnEmptyList() { > =A0 =A0List l =3D new List(); > =A0 =A0l.delete(new Object()); > =A0} > > =A0@Test > =A0@Assumes({"newListIsEmpty","addPutsAnElementIntoAnEmptyList") > =A0public void deleteRemovesAnElement() { > =A0 =A0List l =3D new List(); > =A0 =A0Object o =3D new Object(); > =A0 =A0l.add(o); > =A0 =A0l.delete(o); > =A0 =A0assertThat(l.isEmpty(), is(true)); > =A0} > > In fact in my initial example of tests, there are some additional > assumptions that I didn't make explicit > > > =A0@Test > =A0@Assumes("newListIsEmpty") > =A0public void addPutsAnElementIntoAnEmptyList() { > =A0 =A0... > =A0} > > and > > =A0@Test > =A0@Assumes({"newListIsEmpty","addPutsAnElementIntoAnEmptyList") > =A0public void addIncreasesSizeOfPopulatedListByOne() { > =A0 =A0... > =A0} > > Now you could get some of this functionality via a TestRule... > > You could watch tests to see if they pass, and skip tests annotated > with the annotation if assumed functionality is failing, but that > would result in sporadic failures of, e.g. deleteRemovesAnElement > because of the failing newListIsEmpty being executed _after_ > deleteRemovesAnElement rather than before. > > The simple point is that the test result of deleteRemovesAnElement is > meaningless until its assumptions are true, and while I could code the > assumptions with assumeThat(..)s C&P&S&R is even worse than C&P. > > Another alternative to @Assumes would be to invoke the assumed > method(s) at the start of the test, e.g. > > =A0@Test > =A0public void deleteRemovesAnElement() { > =A0 =A0newListIsEmpty(); // verify assumed functionality > =A0 =A0addPutsAnElementIntoAnEmptyList(); =A0// verify assumed functional= ity > =A0 =A0... > =A0} > > That gets rid of the C&P&S&R, but there are two issues with that: > > =A01. We have to manually invoke any setup/tearDown methods, including > all those of the rules that the test class has... very messy > > =A02. The test fails when the assumed test fails. In actuality we can > say nothing at all about whether deleteRemovesAnElement if a > newListIsEmpty is not passing... yes we could code the test > differently, but that is just moving our assumptions somewhere else. > > I am sure that there are others out there who feel there is a point 3... > > =A03. We already ran those tests why waste time running them again? > > Well the answer to 3 is that these are UNIT tests which should be very > fast, so what is the harm... > > So, in my view, best practice unit testing needs the ability to mark > tests as assuming that other tests are passing, so that those tests > can be skipped when the assumptions are known to be failing or > skipped. [This is a deliberately loaded criteria... if the > org.junit.runner.Request does not include the assumed test, then that > test is neither known failing or known skipped, so we can run the test > and output a warning that the failure may be because of assumed > functionality... the use case of executing one and only one test > repeatedly until you get that test passing] > > The annotation would have implications on test sorting, as any assumed > tests would have to always happen before the assuming tests (as long > as the assumed tests are in the org.junit.runner.Request) > > Also might have to be two annotations, e.g. > > @Assumes(methodNames) > @AssumesClasses(classes) > > though in my view the @AssumesClasses is less critical, as these are > UNIT tests and each test class should be independent to a large > extent. However I am willing to consider that some people may have > many test classes for one class under test, one test class containing > all the tests of the constructors, another testing the Add methods, > etc. in which case an @AssumesClasses annotation makes sense. > > Where tests contain a circular dependency, fail/error both tests > > Ok, let the critique begin! > > -Stephen > > P.S. > > I pinged Kent with an earlier version of this idea... but I think that > he missed the point about eliminating C&P&S&R that this feature would > provide because I didn't frame the idea correctly... > > ---------- Forwarded message ---------- > From: "Kent Beck" > Date: 13 Sep 2011 17:11 > Subject: Re: JUnit and test dependencies > To: "Stephen Connolly" > > Stephen, > > Thank you for articulating your idea so clearly. The short answer is that > no, we don't plan to support dependencies. If I have tests that are slow > enough that I care about dependencies, my most productive option is > generally to work on the design of the software until the tests are fast > enough that I no longer care. That said, my voice is only one of many. The > longer answer is that I encourage you to post your idea on the JUnit mail= ing > list for community discussion. > > Regards, > > Kent > > On Sep 13, 2011, at 8:32 AM, Stephen Connolly wrote: > >> Kent, >> >> Are there any plans for JUnit to support some test dependencies, such as: >> >> public class OnlyRunTestsThatMakeSenseTest { >> >> =A0@Test >> =A0public void basicFunctionalityWorks() { >> =A0 =A0... >> =A0} >> >> =A0@Test >> =A0@AssumesPasses("basicFunctionalityWorks") >> =A0public void advancedFunctionalityWorks() { >> =A0 =A0... >> =A0} >> >> =A0@Test >> =A0@AssumesPasses("basicFunctionalityWorks") >> =A0public void basicFunctionalityWorksWithBevel() { >> =A0 =A0... >> =A0} >> >> =A0@Test >> > =A0@AssumesPasses({"basicFunctionalityWorksWithBevel","advancedFunctional= ityWorks"}) >> =A0public void advancedFunctionalityWorksWithBevel() { >> =A0 =A0... >> =A0} >> >> } >> >> In the above example, no matter what sorting is applied, >> basicFunctionalityWorks will always be run first, and the other three >> tests will only be run if basicFunctionalityWorks passed. >> >> I see the above being completely in the spirit of unit testing, the >> point with the above is that the @Before and @After's will be run >> around each method, you are just saying that there is no point even >> trying to test the advanced functionality when the basic functionality >> is broken, skip those tests which we know cannot pass. That allows the >> person writing advancedFunctionalityWorks to power through the setup >> that depends on the basic functionality and not have to litter their >> advanced test with asserts that are redundant because of the basic >> functionality. Those people who are relying on side-effects should >> really, for unit tests at least, be invoking the method who's >> side-effects they depend on directly within their test method, rather >> than relying on accidental ordering. >> >> Having said that, a second feature that I think would be good is >> something like a @RunAfter and/or @RunBefore which would ensure that >> the test method is run in sequence even if the before or after tests >> fail/are skipped. with @RunAfter and @RunBefore I still think the >> @Before and @After methods should be invoked in-between, this would be >> moving towards more of a general purpose testing framework as opposed >> to being unit-testing focused, but JUnit is just too good ;-) >> >> Thoughts? >> >> -Stephen > > -------------------------------------------------------------------------= ----- > BlackBerry® DevCon Americas, Oct. 18-20, San Francisco, CA > Learn about the latest advances in developing for the > BlackBerry® mobile platform with sessions, labs & more. > See new tools and technologies. Register for BlackBerry® DevCon today! > http://p.sf.net/sfu/rim-devcon-copy1 > _______________________________________________ > Junit-devel mailing list > [email protected] > https://lists.sourceforge.net/lists/listinfo/junit-devel > ---------------------------------------------------------------------------= --- BlackBerry® DevCon Americas, Oct. 18-20, San Francisco, CA Learn about the latest advances in developing for the = BlackBerry® mobile platform with sessions, labs & more. See new tools and technologies. Register for BlackBerry® DevCon today! http://p.sf.net/sfu/rim-devcon-copy1 =