PHPUnit tests real example
I've created a mail wrapper class. I know that there are lots of libraries to send e-mails but i want to learn TDD... So, I've created some tests and i have some code. Now I can set the email address on constructor and validate it... if the email address is wrong, an exception raise up. The email address is the only one required field... I don't have sets and gets because user will setup all email data on constructor.
Now, i'm going to write the send tests. I do开发者_运维知识库n't know how to start it. How could i test if the values are there (subject, mail body, headers) if i don't want to have setters and getters? How could I test if an email could be sent?
Real world TDD examples are hard to me. I've tried to learn about it, i've read lots of things but i cannot test real code.
Thanks.
Since you linked to the mail function, the call to mail
is likely hardcoded into your code. So have a look at
- http://sebastian-bergmann.de/archives/885-Stubbing-Hard-Coded-Dependencies.html
Install the testhelper extension and mock the call to mail
. Then have the mock validate that it got called with the correct values when your wrapper's send method is called, e.g. define a custom mail function somewhere:
function mail_mock()
{
$allThatWasPassedToTheFunction = func_get_args();
return $allThatWasPassedToTheFunction;
}
Then in your send()
test, do something like
public function testSendReceivesExpectedValues()
{
// replace hardcoded call to mail() with mock function
rename_function('mail', 'mail_orig');
rename_function('mail_mock', 'mail');
// use the wrapper
$testClass = new MailWrapper('foo@example.com');
$results = $testClass->send();
// assert the result
$this->assertSame('foo@example.com', $results[0]);
$this->assertSame('Default Title', $results[1]);
$this->assertSame('Default Message', $results[2]);
}
Note that the above assumes your send function will return the result of the mail()
call.
In general, you will always try to substitute an external subsystem, like sendmail or a database or the filesystem with a Mock or a Stub, so you can concentrate on testing your own code in isolation of the external subsystem. You dont need to test that mail
actually works.
Also see http://www.phpunit.de/manual/3.6/en/test-doubles.html
In a pure unit test, you don't really test whether a real email has been sent, but rather whether the appropriate programming unit (the mail
function in this case) has been called. I don't really know how to test whether the mail
function really works as I don't have in depth knowledge of how emailing works under the hood. So, I'll just write how I'd do the unit test.
You can have your class constructor accept an optional argument, a function that does the real work of actually sending the email. By default, it will be the mail
function, but in your test setup, you provide your special function that will actually checks the correct subject, body and headers are present.
The test:
<?php
class EmailerTest extends PHPUnit_Framework_TestCase
{
public function testMailFunctionIsCalledWithCorrectArguments()
{
$actualSubject, $actualBody, $actualHeaders;
$mailFunction = function ($subject, $body, $headers)
use (&$actualSubject, &$actualBody, &$actualHeaders) {
$actualSubject = $subject;
$actualBody = $body;
$actualHeaders = $headers;
};
$emailer = new Emailer($options, $mailFunction);
$emailer->send();
$this->assertEquals('Expected subject', $actualSubject);
$this->assertEquals('Expected body', $actualBody);
$this->assertEquals('Expected headers', $actualHeaders);
}
}
And the class under test:
<?php
class Emailer
{
public function __construct($options, $mailFunction = 'mail')
{
$this->subject = $options->subject;
$this->body = $options->body;
// etc.
$this->mailFunction = $mailFunction;
}
public function send()
{
// find out $subject, $body, $headers, and then...
call_user_func_array($this->mailFunction, array(
$subject,
$body,
$headers
));
}
}
It's some sort of pseudo-code, because I've left some of the values for you to complete or implement. The main point is that you should supply test doubles for the collaborators of the class you're testing.
In this case it's just a function (I've made use of some PHP 5.3) features, but it could be an object instance that you'd pass to the class under test.
I think the class should performs validation for email, subject and other information so I suggest this should be separated to the functions.
When you have the private validation function that must return specific result then you can write the assertion to test it.
this article might be useful.
I'm very new to TDD too and I choose to depend on the IDE (Netbeans) to help me understand this process.
hope this help :)
精彩评论