-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Potential improvement: add support for custom PDO implementation #20
base: master
Are you sure you want to change the base?
Conversation
src/Codeception/Lib/Driver/Db.php
Outdated
@@ -3,9 +3,12 @@ | |||
namespace Codeception\Lib\Driver; | |||
|
|||
use Codeception\Exception\ModuleException; | |||
use Vimeo\MysqlEngine\FakePdo; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I am aware this won't break even if the package isn't installed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is no longer present
Fast moving target; the php-mysql-enging package has a change queued up for next release that splits the class for PHP7 or PHP8; when that hits I'll update this PR if not yet merged, or follow up a new PR if this did get merged. |
I see a big problem here. This module does not interact with application which is being tested, but with database only. How are you going to make application code use the same instance of FakePdo? |
Thanks for checking this out! So I might have mentioned it above, but the
actual fake "server" is a static class so any instance of its use in the
code (test harness or application) will use the same test data.
It's worth also pointing out therefore that this will only work for
integration tests where Codeception is calling application code - it won't
work for functional or acceptance tests because there's a network request
gap.
However given that different suites may (and generally should) use
different module configurations this is still usable for those types of
test without negative effects on acceptance tests.
Hope that makes sense, I've got a working demo that can set data in the
database but just need to implement one more piece of SQL syntax in the
parser to work with how Codeception does MySQL inserts.
…On Wed, 3 Feb 2021, 10:28 pm Gintautas Miselis, ***@***.***> wrote:
I see a big problem here. This module does not interact with application
which is being tested, but with database only.
How are you going to make application code use the same instance of
FakePdo?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#20 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAJLLCYFNCEACFMWDAYPCZ3S5HEW5ANCNFSM4XAWEKHA>
.
|
If application uses PDO directly, you have to make the same changes in application code as you made here and come up with a way to pass There will still be differences between behaviour of FakePDO and real mysql. Finally, php-mysql-engine is so new that I won't merge this PR for at least a month until it stabilizes. |
…lass name. Also add option to config docs at the top
Ultimately this will vary on a case-by-case basis. Many frameworks will support Dependency Injection and allow a PDO instance to be passed in to the framework itself, or in the case of an ORM, may allow the ORM to be injected and this also allows PDO to be injected. If a framework creates a PDO instance within the application then it's not possible to stub it out - however that would suggest the framework could have other problems being tested. For example in the main (private) framework I maintain, our Codeception tests already inject a custom PDO library which has extra debug logging built in to analyse failed tests. So I imagine this is a use case people might already be handling. So far however we've never required that of the PDO instance in Codeception, because the module already provides debug output. In short though - many frameworks can inject PDO, but if they can't then the whole concept of fake PDO is useless to them anyway.
Yes, this is true. Not everything is implemented. So it will not work for every case. A user can't necessarily plug and play with Fake PDO if their application makes a lot of use of triggers or views. However this is already the case with SqlLite - if your application utilises any specific features of one database (e.g. MySQL) then many commands will just fail with SqlLite. However, in the case of FakePdo it would be possible to fix these yourself - it stands to reason if you're developing a large PHP application that your team would be able to write PHP code to resolve incompatibilities, whereas if I meet a problem with SqlLite I likely lack the skills or ability to ask the maintainers to make it more compatible with MySQL.
These are very reasonable concerns. I've modified my implementation significantly in light of this. The implementation now simply allows the module configuration to declare a config item Do you think this would be suitable for merging sooner? |
…o modify class not exists to throw exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a few notes to why I made certain decisions which should make reviewing easier.
@@ -1,3 +1,4 @@ | |||
/.idea/ | |||
/vendor/ | |||
/composer.lock | |||
composer.phar |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove this if needed but seemed sensible to include it
} | ||
|
||
if (!class_exists($pdo_class)){ | ||
throw new ModuleException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially this returned the fallback value of \PDO::class
but actually throwing an exception is more helpful to the user - they can resolve by correcting the class name or by simply removing the config item.
$dbh = new \PDO($dsn, $user, $password, $options); | ||
$class_name = self::pdoClass($pdo_class); | ||
$dbh = new $class_name($dsn, $user, $password, $options); | ||
self::assertIsPdo($dbh, $pdo_class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assert is necessary so we don't get something that accepts the right arguments but doesn't provided expected methods and/or fails expected typehints down the line.
$this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); | ||
|
||
$this->dsn = $dsn; | ||
$this->user = $user; | ||
$this->password = $password; | ||
$this->options = $options; | ||
$this->pdo_class = $pdo_class; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storing this the same as the rest so it can be passed back in if necessary (e.g. sqllite reconnection)
@@ -570,7 +571,8 @@ private function connect($databaseKey, $databaseConfig) | |||
|
|||
try { | |||
$this->debugSection('Connecting To Db', ['config' => $databaseConfig, 'options' => $options]); | |||
$this->drivers[$databaseKey] = Driver::create($databaseConfig['dsn'], $databaseConfig['user'], $databaseConfig['password'], $options); | |||
$pdo_class = array_key_exists('pdo_class', $databaseConfig) ? $databaseConfig['pdo_class'] : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds a bit more bulk but without it a lot of tests fail due to notices.
Thanks, @M1ke So I like php-mysql-engine but I am not sure this is the right place to use it. I expect the typical use case would be a PHP application that uses a lot of PDO statement and we need to have an integration tests for such kind of application. In this case same instance of FakePDO should be injected into app and into tests. For instance, creating a new connection from FakePDO is not an option as we must rely on the same connection an application already uses. So my suggestion is following:
|
Hi @DavertMik Thanks for looking over this. As it stands this implementation doesn't prejudice the module to any specific PDO implementation. It simply allows the class to be named up front, rather than created in the module constructor as a fixed class name. This would make it suitable for any PDO implementation, whilst avoiding coupling of the module to fake pdo or any other library. In the specific case of fake PDO the change as made here will work "as is" for integration testing. Thus for the addition of a single config value we get the ability to use fake PDO or any other library. If it's not possible to merge this then most likely I'll just fork it, as the work required to make a new module fully compatible would be quite high and require updates if this implementation changed. Whereas if this PR can be merged then it just makes this module slightly more flexible without any burden of extra maintenance. |
@DavertMik what do we think to merging this on the basis that it has no dependency on FakePDO (or any other PDO implementation) but that it simply allows an extra degree of flexibility for someone who would like to use an alternative PDO implementation (and without having any negative effect for those who don't)? |
May be exists some alternative for pgsql? |
This is quite a speculative PR so please consider it one for discussion rather than immediate merge or refusal either way. I thought it better to open as a PR rather than an issue so that we can discuss the code implications.
There's a new tool released that provides a fake implementation of PDO allowing for database driven tests to be run against essentially a fake database implemented in PHP. This could significantly reduce test runtimes by avoiding database network connections/sockets and by reducing disk i/o. It is currently limited in some ways - it doesn't support triggers or views, and some parsing is incomplete, but the core is there.
I have worked on implementing this in Codeception and have been able to run an initial database creation from a schema file, and have got to running tests. It fails at
haveInDatabase
for mysql specifically because the parser lacks support forshow indexes
but this can be worked on.To implement it in the Codeception module I added a keyword
fake:
to the start of theDSN
which gets stripped off and used to start the fake PDO driver rather than a real one. FakePdo uses static storage under the hood to represent a database, meaning that the same FakePdo created on module initialization would be available for userland access if test code injected FakePdo instead of real PDO into application code.As far as I can see merging this would have no impacts unless someone chose to add
fake:
to the start of their DSN; at this point they'd need to have installed thevimeo/php-mysql-engine
package or Codeception would crash on startup. I thought that preferable to making that package a dependency.Let me know if this is something we might merge, or if there are better ways to implement this as an optional plugin without having to re-implement all the other features of this module.