This is what I have a hard time with as well. It's not so much the concept of testing first, it's what to test. I guess it's probably best to error on the side of testing too much though?matthijs wrote:Yeah, I guess this is what I'm referring to. As I'm just starting with Simpletest, I'm constantly asking myself questions like:
"Ok, this method $foo->save(); . Should I just test if it returns a True result? Should I also test if what has been saved is indeed now in the database? Should I also test if what has been saved to the database is in fact equal to what data I had in the first place?"
etc etc
Why TDD leads to cleaner code
Moderator: General Moderators
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
Test mocks first (applies mostly to composition) and encourages composition.
Make good use of accessors since it's near impossible to make expectations on changes to properties without them.
Don't test the *blatantly* obvious (as in basic setters/getters) unless there is some underlying reason you're leading up to.
Don't test your implementation. That just comes about with the interface you choose, so test the interface and the behaviour you expect (mock expectations, return values, exceptions...).
But yes, (and I'm relatively new to TDD too
) having a better test coverage isn't a bad thing... except for the extra superfluous code you need to sift through to get to the point 
Make good use of accessors since it's near impossible to make expectations on changes to properties without them.
Don't test the *blatantly* obvious (as in basic setters/getters) unless there is some underlying reason you're leading up to.
Don't test your implementation. That just comes about with the interface you choose, so test the interface and the behaviour you expect (mock expectations, return values, exceptions...).
But yes, (and I'm relatively new to TDD too
That is a very good point... I need to learn how to write mock objects a little better. I remember reading about this recently. Setting up mock objects to ACT like the pieces of data you're working with. This way you aren't testing whether a certain detail of the database (that has nothing to do with your code) works. You are only testing YOUR code.
If anybody wants to drop by and help me with some tdd stuff on the devnetstore forum, that would be super.
http://www.devnetstore.com/forum/viewto ... =19&hilit=
If anybody wants to drop by and help me with some tdd stuff on the devnetstore forum, that would be super.
http://www.devnetstore.com/forum/viewto ... =19&hilit=
- Maugrim_The_Reaper
- DevNet Master
- Posts: 2704
- Joined: Tue Nov 02, 2004 5:43 am
- Location: Ireland
I guess the problem is that people initially think of testing as going through all your code and testing every piece of it. With unit testing you are only testing the interface, i.e. the public methods and public properties to ascertain whether each task a class is designed to perform is done correctly.
You'll hear people talk about testing the interface and not the implementation. This is because in refactoring, you might constantly be changing the implementation (e.g. replacing one block of code with something more optimised, or just cutting blocks of duplicated code out of public methods and extracting them into reuseable private methods) but the interface and the class behaviour should remain constant. This is one reason why refactoring is so much easier with a set of unit tests. With each refactor, you can easily verify the class still behaves as expected.
You'll hear people talk about testing the interface and not the implementation. This is because in refactoring, you might constantly be changing the implementation (e.g. replacing one block of code with something more optimised, or just cutting blocks of duplicated code out of public methods and extracting them into reuseable private methods) but the interface and the class behaviour should remain constant. This is one reason why refactoring is so much easier with a set of unit tests. With each refactor, you can easily verify the class still behaves as expected.
Well, in your case I believe the class interacted directly with a database, so checking database contents was the right step. If however your $foo object interacted with a distinct DB abstraction layer (e.g. a library like ADOdb or MDB) then the interface to that library could be mocked. For ADOdb, for example, I have an "outline class" which is just an simple object with a list of empty public methods. Since you can't mock and entire library, you can use outline classes to hold a replica of the public interface and mock it instead - a handy method for single facing libraries which can't be directly mocked for some reason. The other thing with mocks is that lets you test a class which depends on a second class which does not yet exist - somethingYeah, I guess this is what I'm referring to. As I'm just starting with Simpletest, I'm constantly asking myself questions like:
"Ok, this method $foo->save(); . Should I just test if it returns a True result? Should I also test if what has been saved is indeed now in the database? Should I also test if what has been saved to the database is in fact equal to what data I had in the first place?"
etc etc
Indeed. This is something that only very recently became clear. And which I think is quite cool.maugrim wrote:You'll hear people talk about testing the interface and not the implementation. This is because in refactoring, you might constantly be changing the implementation (e.g. replacing one block of code with something more optimised, or just cutting blocks of duplicated code out of public methods and extracting them into reuseable private methods) but the interface and the class behaviour should remain constant. This is one reason why refactoring is so much easier with a set of unit tests. With each refactor, you can easily verify the class still behaves as expected.
Something else I thought about was that writing the tests/test methods is in fact a bit like writing a formal specification of the application or pieces of the application. However, with the important difference that instead of a big bundle of paper report gathering dust somewhere you have a specification you really use and follow, which you are forced to adhere to. If you want to pass green that is
Exactly, not only does it ensure that you follow your specification, but it also ensures that you actually write tests. This is definitely the way I want to start developing. Thanks for reassuring me everybody!matthijs wrote:Something else I thought about was that writing the tests/test methods is in fact a bit like writing a formal specification of the application or pieces of the application. However, with the important difference that instead of a big bundle of paper report gathering dust somewhere you have a specification you really use and follow, which you are forced to adhere to. If you want to pass green that is
- Maugrim_The_Reaper
- DevNet Master
- Posts: 2704
- Joined: Tue Nov 02, 2004 5:43 am
- Location: Ireland
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Perhaps if you thought about TDD as iterating Design in smaller chunks rather than about Coding it would help. Coding is the easy part -- good design is the hard part. TDD forces you to only design a small finite part of your application at a time. And it forces you to only implement the parts of the design that you truly understand. As you do that, more of the design is revealed. So the question is not what to test first or to code first, but what to design first. What is the part of the design that you presently know the most about ... start there, write a test to specify/verify that design and then code it. Then (now smarter) move on to the next piece of the design...The Ninja Space Goat wrote:This is what I have a hard time with as well. It's not so much the concept of testing first, it's what to test. I guess it's probably best to error on the side of testing too much though?
(#10850)
- Ollie Saunders
- DevNet Master
- Posts: 3179
- Joined: Tue May 24, 2005 6:01 pm
- Location: UK
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
Mock objects are simple objects that share the same interface as the dependancy object of the class/object that you are testing needs.
Because you are testing the parent object and not the mock object, you don't need any functionality other than to return expected values.
An example mock db object:
Now any object I am testing that has a dependancy on my database object will use that in the test.
Because you are testing the parent object and not the mock object, you don't need any functionality other than to return expected values.
An example mock db object:
Code: Select all
<?php
class Mock_DB_Object implements Original_DB_Class
{
public function fetchResultsArray()
{
return array(1,2,3,4,5);
}
public function fetchColumn($row, $col)
{
return '123';
}
public function connect($host, $user, $pass)
{} // no return value, only implementing the interface
public function disconnect()
{} // as above
// etc..
}
?>- Ollie Saunders
- DevNet Master
- Posts: 3179
- Joined: Tue May 24, 2005 6:01 pm
- Location: UK
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
Jenk... Mocks aren't just placeholders. Stubs do that job. Mocks can be tested too. PartialMocks can also be used to make expectations on what the class will call within itself... though needing a partial mock for that often indicates something could do with a re-think.
Mocks are often generated from interfaces too, before you ever get as far as writing a class:
Mocks are often generated from interfaces too, before you ever get as far as writing a class:
Code: Select all
interface Foo {
public function doFoo(Bar $bar);
public function undoFoo();
}
Mock::Generate("Foo");
class TestOfFoo extends UnitTestCase {
public function testSomething() {
$mock = new MockFoo();
$mock->expectOnce("doFoo");
$mock->doFoo(new Bar());
}
}