Miten yksikkötestataan oikeaoppisesti yksinkertaisia malleja?
Minkälaiset yksikkötestitapaukset esim. seuraavalle luokalle:
class Blog extends PdoAbstract { public function getPosts($limit = 10, $status = 1) { if(!is_numeric($limit) || $limit < 0) throw new InvalidArgumentException("Limiting result with non numeric value: " . $limit); if(!is_numeric($status) || $status > 1 || $status < 0) throw new InvalidArgumentException("Limiting result by status with invalid value: " . $status); $query = $this->pdo()->prepare("SELECT * FROM posts WHERE status = ? ORDER BY datetime DESC LIMIT " . intval($limit)); $query->execute(array($status)); return $query->fetchAll(); } public function getPostById($id) { $query = $this->pdo()->prepare("SELECT * FROM posts WHERE id = ?"); $query->execute(array($id)); return $query->fetch(); } public function insertPost(array $postData) { $postData[] = date("Y-m-d H:i:s"); $query = $this->pdo()->prepare("INSERT INTO blog.posts (subject, message, datetime) VALUES (?, ?, ?)"); $query->execute($postData); return $query->rowCount(); } public function updatePost($id, array $postData) { $postData[] = $id; $query = $this->pdo()->prepare("UPDATE blog.posts SET subject = ?, message = ? WHERE id = ?"); $query->execute($postData); return $query->rowCount(); } public function deletePost($postId) { $query = $this->pdo()->prepare("UPDATE blog.posts SET status = 0 WHERE id = ?"); $query->execute(array($postId)); return $query->rowCount(); } }
Olenko oikeilla jäljillä:
<?php include 'init.php'; use application\models\BlogManager\Blog; /* * Testing Blog */ class BlogTest extends PHPUnit_Framework_TestCase { private $blog; public function setUp() { $this->blog = new Blog; } public function testInvalid_getPosts1() { $this->setExpectedException('InvalidArgumentException'); $this->blog->getPosts(null); } public function testInvalid_getPosts2() { $this->setExpectedException('InvalidArgumentException'); $this->blog->getPosts("String"); } public function testInvalid_getPosts3() { $this->setExpectedException('InvalidArgumentException'); $this->blog->getPosts(-1); } public function testInvalid_getPosts4() { $this->setExpectedException('InvalidArgumentException'); $this->blog->getPosts(10, null); } public function testInvalid_getPosts5() { $this->setExpectedException('InvalidArgumentException'); $this->blog->getPosts(10, "String"); } public function testInvalid_getPosts6() { $this->setExpectedException('InvalidArgumentException'); $this->blog->getPosts(10, 3); } public function testInvalid_getPosts7() { $this->setExpectedException('InvalidArgumentException'); $this->blog->getPosts(10, -1); } public function testInvalid_getPostById() { $this->assertFalse($this->blog->getPostById(null)); $this->assertFalse($this->blog->getPostById("String")); $this->assertFalse($this->blog->getPostById(-5)); $this->assertFalse($this->blog->getPostById(0123456789)); } public function testValid_insertPost() { $data = array( "UnitestSubject", "UnitestMessage" ); $this->assertEquals($this->blog->insertPost($data), 1); } public function testPDOException_insertPost1() { $this->setExpectedException('PDOException'); $this->blog->insertPost(array()); } public function testPDOException_insertPost2() { $this->setExpectedException('PDOException'); $this->blog->insertPost(array(1)); } public function testPDOException_insertPost3() { $this->setExpectedException('PDOException'); $this->blog->insertPost(array(1, 2, 3)); } public function testValid_insert_select_delete() { $data = array( "UnitestSubject1", "UnitestMessage1") ); $this->assertEquals($this->blog->insertPost($data), 1); $id = $this->blog->pdo()->lastInsertId(); $post = $this->blog->getPostById($id); $this->assertType('array', $post); $this->assertEquals($post["subject"], $data[0]); $this->assertEquals($post["message"], $data[1]); $this->assertEquals($this->blog->deletePost($id), 1); $post = $this->blog->getPostById($id); $this->assertEquals($post["status"], 0); } public function testValid_insert_select_update() { $data = array( "UnitestSubject2", "UnitestMessage2" ); $this->assertEquals($this->blog->insertPost($data), 1); $id = $this->blog->pdo()->lastInsertId(); $post = $this->blog->getPostById($id); $this->assertType('array', $post); $this->assertEquals($post["subject"], $data[0]); $this->assertEquals($post["message"], $data[1]); $newdata = array( "UpdateUnitestSubject3", "UpdateUnitestMessage3" ); $this->assertEquals($this->blog->updatePost($id, $newdata), 1); $updatedpost = $this->blog->getPostById($id); $this->assertType('array', $updatedpost); $this->assertEquals($updatedpost["subject"], $newdata[0]); $this->assertEquals($updatedpost["message"], $newdata[1]); } }
Ei suoranaisesti liity testaukseen, mutta minua häiritsee hieman nämä rivit:
$id = $this->blog->pdo()->lastInsertId();
Blog-luokka voisi itse tarjota tuon toiminnallisuuden niin ettei sitä käytettäessä tarvitsi hakea tietoa suoraan pdo:lta tai välttämättä edes tietää että Blog-luokan taustalla ylipäänsä on pdo. insertPost-metodi voisi esimerkiksi suoraan palauttaa asetetun id:n tai null jos tallennus epäonnistui. Nykyinen row countin palauttaminenhan ei tuo tuohon nähden mitään lisäarvoa koska se on aina 1 tai 0.
Toisekseen häiritsee nuo random-luvut tuolla testidatassa ( "UnitestSubject".rand()...). Yleisesti ottaen minusta on huono tapa käyttää satunnaisuutta testidatassa, koska silloin testitkin voivat käyttäytyä satunnaisesti. Koodista ei suoraan ilmene, mutta jos esimerkiksi subject ja message (yhdessä tai erikseen) on määritelty kantaan uniikeiksi niin noista tulee satunnaisesti testausvirheitä.
Kolmanneksi, et lainkaan testaa Blog-luokan ehkä monimutkaisinta kohtaa eli sitä että getPosts palauttaa juuri halutut postit halutussa järjestyksessä ja ainoastaan ne eikä mitään ylimääräistä.
Tukki kirjoitti:
....
Kiitos vastauksestasi, hyviä pointteja.
Täytyy päivittää hieman..
Sen verran yleisesti sovelluksen jäsentämisestä, että nyt sinulla on ongelmana tuo Tukin mainitsema kovakoodaus PDO:n suhteen Blog-luokassa. Tämä on isku munille mm. testattavuuden suhteen (vältä tightly coupled koodia).
Sen sijaan pyri käyttämään "loosely coupled" objekteja, ja sitomaan ne yhteen DI:n avulla.
Itse varsinainen oikean tietokannan testaus on pykälää monimutkaisempi operaatio, ja kannattaneekin aloittaa PHPUnitin manuaalista.
Ja kannattaa myös pyrkiä nimeämään testit kuvaavasti. Tuollaiset testPDOException_insertPostX ovat vähintäänkin kyseenalaisia nimiä. Tuollaisesta nimestä kun ei saa käsitystä että mikä meni tarkalleen ottaen pieleen.
Aihe on jo aika vanha, joten et voi enää vastata siihen.