I'm trying to automate a user scenario that involves two websites with no common base url. How can I achieve this? Right now I have unsuccessfully tried altering global variables but they are reset for each test.
public $check = true;
protected function setUp() {
$this->setBrowser("*googlechrome");
if ($this->check==true)
$this->setBrowserUrl("SITE A");
else
$this->setBrowserUrl("SITE B");
$this->setPort(4444);
$this->setHost("0.0.0.0");
}
public testA() { //requires SITE A, set check to false }
public testB() { //requires SITE B }
Your code won't work since setUp() is executed before every test case is run.
Why not being more explicit about what you're trying to do? Try the following:
private $sites = array('A' => 'a.com', 'B' => 'b.com');
protected function setUp() {
$this->setBrowser("*googlechrome");
$this->setPort(4444);
$this->setHost("0.0.0.0");
}
public function testA()
{
$this->useSite('A');
}
public function testB()
{
$this->useSite('B');
}
private function useSite($site)
{
$this->setBrowserUrl($this->sites[$site]);
}
Related
I am very new to TDD. I am using phpunit 7.4x-dev. I have the following abstract class that I am trying to develop unit tests for.
use PDO;
abstract class Model {
protected static function getDB() {
static $db = null;
if ($db === null) {
$db = new PDO(ConfigDatabase::DSN, ConfigDatabase::USER, ConfigDatabase::PASSWORD);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $db;
}
}
I have created the following test to get around the need to deal with the static protected method. And it works if I provide "ConfigureDatabase" class.
use PHPUnit\Framework\TestCase;
class ModelTest extends TestCase {
function newMockClass(){
$stub = new class() extends Model{
function getStaticMethod($methodName){
return self::$methodName();
}
};
return $stub;
}
public function testDatabaseExists() {
$stub = $this->newMockClass();
$db = $stub->getStaticMethod('getDB');
$this->assertInstanceOf(PDO::class,$db);
}
}
Since I do not want my tests to rely on any actual database, How would I fake the calls to PDO.
Following Dormilich suggestion I developed a database interface, just in case I decide later I do not want to use PDO.
interface CRUDImp {
function __construct($datbaseBridgeLikePDO);
...
}
Next I wrote my tests for the constructor. I used setup to make sure I was starting with a fresh mock of \PDO.
class PDOWrapperTest extends TestCase {
private $pdoMock;
private $db;
function setup() {
$this->pdoMock = $this->createMock('\PDO');
$this->db = new PDOWrapper($this->pdoMock);
}
public function testWrapperExists() {
$this->pdoMock->method('getAttribute')->willReturn(\PDO::ERRMODE_EXCEPTION);
$db = new PDOWrapper($this->pdoMock);
$x = $db instanceof CRUDImp;
$this->assertTrue($x);
}
/**
* #expectedException \Exception
*/
public function testNonPDOPassedToConstructor() {
$mock = $this->createMock('\Exception');
$x = new PDOWrapper($mock);
}
...
}
Since PHP is loosely typed I check to make sure that the class passed to the constructor was an instance of \PDO. I implemented the concrete class as follows
class PDOWrapper implements CRUDImp {
private $pdo;
private $dataOutputType = \PDO::FETCH_ASSOC;
public function __construct($pdo) {
if (!($pdo instanceof \PDO)) {
throw new \Exception("PDOWrapper must be passed instance of \PDO");
}
$attr_Errmode = $pdo->getAttribute(\PDO::ATTR_ERRMODE);
if ($attr_Errmode !== \PDO::ERRMODE_EXCEPTION) {
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
$this->pdo = $pdo;
}
...
}
Now that I have an independent database wrapper the original Model tests are at the moment trivial and no longer needed. The abstract class Model was modified as follows:
abstract class Model {
protected $database=null;
function __construct(CRUDWrapper $database) {
$this->database = $database;
}
...
}
So for those not familiar with dependency injection I found the following links helpful:
http://php-di.org/doc/understanding-di.html
https://codeinphp.github.io/post/dependency-injection-in-php/
https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
Hope this shortens someone's work.
I'm starting to learn OOP and I know it's hard to build good, quality, testable code and I'm afraid to make some architectural mistake and the beginning because it's harder to refactor later.
Currently, I'm working on Laravel and I need a component (a small part of a program) to calculate online advertising statistics (CPM, EPC and so on) uisng cronjob. For this purpose, I need to collect data from the database, calculate statistic(s) and store it to related table. This need to be run through CLI using cronjob. Calculation of stats should be done if possible by SQL, but it not always can be done with current architecture.
So I need to create some reusable component which can be easily extended with new stat calculation logic, either with just fetch already calculated logic from DB and store it or fetch, calculate and store to DB. And to have ability for futuhre to use it easily in any part of application not just by CLI.
To run it from CLI I'm using Laravel command with scheduling:
class StatsComamnd extends Command
{
protected $signature = 'project:calculatestats {statName}';
public function __construct(StatsService $statsService){
parent::__construct();
$this->statsService = $statsService;
}
public function handle() {
$method = $this->argument('statName');
if(!method_exists($this, $method)) {
$this->error('Invalid stat name provided!');
}
$this->{$method}();
}
public function networkOffers():void {
$this->stastService->setStatsHandler(app(OffersNetworkStatsHandler::class))->handle();
}
public function networkOffersCpm():void{
app(OffersNetworkCpmHandler::class)->handle();
}
public function networkOffersEpc:void{
app(OffersNetworkEpcHandler::class)->handle();
}
public function networkSurveys():void{
app(SurveysNetworkHandler::class)->handle();
}
public function networkSurveysCpm():void{
app(SurveysNetrworkCpmHandler::class)->handle();
}
public function networkSurveysEpc:void{
app(SurveysNetworkEpcHandler::class)->handle();
}
//...other handlers, like countryOffersCpm, toolSpecificOffersCpm and so on
}
SurveysNetrworkCpmStatsHandler:
/** This handle responsible of collectiong, calculating and storing network wide survey CPMs. We can't calculate CPM inside DB, so here we are going to use CpmCalculator */
class SurveysNetrworkCpmStatsHandler implements StatsHandlerInterface {
private $surveyImpressionsRepo;
private $statsRepo;
private $vcPointRepo;
private $calculator;
public function __construct(
SurveyImpressionRepositoryInterface $surveyImpressionRepository,
SurveyStatsRepositoryInterface $statsRepository,
VcPointRepositoryInterface $vcPointRepository,
CpmCalculator $calculator
){
$this->surveyImpressionsRepo = $surveyImpressionRepository;
$this->calculator = $calculator;
$this->vcPointRepo = $vcPointRepository;
$this->statsRepo = $statsRepository;
}
public function handle() {
$stats = [];
list($impressions, $conversions) = $this->fetchStatisticData();
foreach ($impressions as $impression) {
$sid = $impression->survey_id;
$conversion = $conversions->first(function($conversion) use ($sid) {
return $conversion->survey_id === $sid;
});
if(!isset($conversion)) {
continue;
}
$stat = new \SurveyNetworkCpmStat();
$stat->offer_id = $impression->offer_id;
$stat->survey_id = $sid;
$stat->mobile_cpm = $this->calculator->setConversionCount($conversion->conversions_count_mobile)->setImpressionsCount($impression->unique_impressions_count_mobile)->setPayoutSum($conversion->payout_sum_mobile)->calculate();
$stat->desktop_cpm = $this->calculator->setConversionCount($conversion->conversions_count_desktop)->setImpressionsCount($impression->unique_impressions_count_desktop)->setPayoutSum($conversion->payout_sum_desktop)->calculate();
$stat[] = $stat->toArray();
}
$this->store($stats)
}
private function fetchStatisticData(){
$impressions = $this->surveyImpressionsRepo->getImpressionsForNetworkCpm();
$conversions = $this->vcPointRepo->getConversionsForSurveyNetworkCpm();
return [$impressions, $conversions];
}
private function store($stst): bool{
$this->statsRepo->insert()
}
}
SurveysNetrworkStatsHandler:
/** This handle responsible of collectiong, calculating and storing all network wide survey stats.*/
class SurveysNetrworkStatsHandler implements StatsHandlerInterface {
private $cpmHandler;
private $epcHandler;
private $statsRepo;
public function __construct(
SurveysNetrworkCpmStatsHandler $cpmHandler,
SurveysNetrworkEpcStatsHandler $epcHandler,
SurveyStatsRepositoryInterface $statsRepository
){
$this->cpmHandler = $cpmHandler;
$this->epcHandler = $epcHandler;
$this->statsRepo = $statsRepository;
}
public function handle() {
$this->cpmHandler->handle();
$this->epcHandler->handle();
}
}
OffersNetrworkCpmStatsHandler:
etrworkCpmStatsHandler:
/** This handle responsible of collectiong, calculating and storing network wide offers CPMs. We can calculate CPM inside DB, so here do not need any Calculator */
class OffersNetrworkCpmStatsHandler implements StatsHandlerInterface {
private $surveyImpressionsRepo;
private $statsRepo;
public function __construct(
SurveyImpressionRepositoryInterface $surveyImpressionRepository,
SurveyStatsRepositoryInterface $statsRepository
){
$this->surveyImpressionsRepo = $surveyImpressionRepository;
$this->statsRepo = $statsRepository;
}
public function handle() {
$stats = [];
$stats = $this->fetchStatisticData();
$this->store($stats)
}
private function fetchStatisticData(){
return $this->surveyImpressionsRepo->getCpm();
}
private function store($stst): bool{
$this->statsRepo->insert()
}
}
CpmCalculator:
/** Class NetworkCpmCalculator. This object responsible for calculation of CPM. Formula to calculate cpm is payout_sum/(impressions_count/1000) */
class NetworkCpmCalculator implements StatsCalculatorInterface {
private $payoutSum = 0;
private $impressionsCount = 0;
private $conversionsCount = 0;
public function setPayoutSum(float $payoutSum = null):self{
$this->payoutSum = $payoutSum;
return $this;
}
public function setImpressionsCount(int $impressionsCount = null):self{
$this->impressionsCount = $impressionsCount;
return $this;
}
public function setConversionCount(int $conversionsCount = null):self{
$this->conversionsCount = $conversionsCount;
return $this;
}
public function calculate():int{
if(!$this->validate()) {
return null;
}
return $this->payoutSum/($this->impressionsCount/1000);
}
//validation here
}
I remove all validation logic here and interfaces to reduca amount of code.
Can anyone suggest any improvements, maybe I can use some patterns here? Thanks for any suggestions.
I'm trying to test small pieces of code. I do not want test one of the method and used Mockito.doNothing(), but this method was still run. How can I do that?
protected EncoderClientCommandEventHandler clientCommandEventHandlerProcessStop = new EncoderClientCommand.EncoderClientCommandEventHandler() {
#Override
public void onCommandPerformed(
EncoderClientCommand clientCommand) {
setWatcherActivated(false);
buttonsBackToNormal();
}
};
protected void processStop() {
EncoderServerCommand serverCommand = new EncoderServerCommand();
serverCommand.setAction(EncoderAction.STOP);
checkAndSetExtension();
serverCommand.setKey(getArchiveJobKey());
getCommandFacade().performCommand(
serverCommand,
EncoderClientCommand.getType(),
clientCommandEventHandlerProcessStop);
}
#Test
public void testClientCommandEventHandlerProcessStop() {
EncoderClientCommand encoderClientCommand = mock(EncoderClientCommand.class);
Mockito.doNothing().when(encoderCompositeSpy).buttonsBackToNormal();
when(encoderCompositeSpy.isWatcherActivated()).thenReturn(false);
encoderCompositeSpy.clientCommandEventHandlerProcessStop.onCommandPerformed(encoderClientCommand);
I've found the problem. One of the variable is already mocked in buttonsBackNormal().
Currently, openGoogle() does get called for each test case with the correct parameters. The problem is that setBrowser does not appear to be working properly. It does set the first time and completes the test successfully. However, when openGoogle() gets invoked for the second time it continues to use the first browser instead of using the new browser specified.
using NFramework = NUnit.Framework;
...
[NFramework.TestFixture]
public class SampleTest : FluentAutomation.FluentTest
{
string path;
private Action<TinyIoCContainer> currentRegistration;
public TestContext TestContext { get; set; }
[NFramework.SetUp]
public void Init()
{
FluentAutomation.Settings.ScreenshotOnFailedExpect = true;
FluentAutomation.Settings.ScreenshotOnFailedAction = true;
FluentAutomation.Settings.DefaultWaitTimeout = TimeSpan.FromSeconds(1);
FluentAutomation.Settings.DefaultWaitUntilTimeout = TimeSpan.FromSeconds(30);
FluentAutomation.Settings.MinimizeAllWindowsOnTestStart = false;
FluentAutomation.Settings.ScreenshotPath = path = "C:\\ScreenShots";
}
[NFramework.Test]
[NFramework.TestCase(SeleniumWebDriver.Browser.Firefox)]
[NFramework.TestCase(SeleniumWebDriver.Browser.InternetExplorer)]
public void openGoogle(SeleniumWebDriver.Browser browser)
{
setBrowser(browser);
I.Open("http://www.google.com/");
I.WaitUntil(() => I.Expect.Exists("body"));
I.Enter("Unit Testing").In("input[name=q]");
I.TakeScreenshot(browser + "EnterText");
I.Click("button[name=btnG]");
I.WaitUntil(() => I.Expect.Exists(".mw"));
I.TakeScreenshot(browser + "ClickSearch");
}
public SampleTest()
{
currentRegistration = FluentAutomation.Settings.Registration;
}
private void setBrowser(SeleniumWebDriver.Browser browser)
{
switch (browser)
{
case SeleniumWebDriver.Browser.InternetExplorer:
case SeleniumWebDriver.Browser.Firefox:
FluentAutomation.SeleniumWebDriver.Bootstrap(browser);
break;
}
}
}
Note: Doing it this way below DOES work correctly - opening a separate browser for each test.
public class SampleTest : FluentAutomation.FluentTest {
string path;
private Action currentRegistration;
public TestContext TestContext { get; set; }
private void ie()
{
FluentAutomation.SeleniumWebDriver.Bootstrap(FluentAutomation.SeleniumWebDriver.Browser.InternetExplorer);
}
private void ff()
{
>FluentAutomation.SeleniumWebDriver.Bootstrap(FluentAutomation.SeleniumWebDriver.Browser.Firefox);
}
public SampleTest()
{
//ff
FluentAutomation.SeleniumWebDriver.Bootstrap();
currentRegistration = FluentAutomation.Settings.Registration;
}
[TestInitialize]
public void Initialize()
{
FluentAutomation.Settings.ScreenshotOnFailedExpect = true;
FluentAutomation.Settings.ScreenshotOnFailedAction = true;
FluentAutomation.Settings.DefaultWaitTimeout = TimeSpan.FromSeconds(1);
FluentAutomation.Settings.DefaultWaitUntilTimeout = TimeSpan.FromSeconds(30);
FluentAutomation.Settings.MinimizeAllWindowsOnTestStart = false;
path = TestContext.TestResultsDirectory;
FluentAutomation.Settings.ScreenshotPath = path;
}
[TestMethod]
public void OpenGoogleIE()
{
ie();
openGoogle("IE");
}
[TestMethod]
public void OpenGoogleFF()
{
ff();
openGoogle("FF");
}
private void openGoogle(string browser)
{
I.Open("http://www.google.com/");
I.WaitUntil(() => I.Expect.Exists("body"));
I.Enter("Unit Testing").In("input[name=q]");
I.TakeScreenshot(browser + "EnterText");
I.Click("button[name=btnG]");
I.WaitUntil(() => I.Expect.Exists(".mw"));
I.TakeScreenshot(browser + "ClickSearch");
} }
Dev branch: The latest bits in the Dev branch play nicely with NUnit's parameterized test cases in my experience.
Just move the Bootstrap call inside the testcase itself and be sure that you manually call I.Dispose() at the end. This allows for proper browser creation when run in this context.
Here is an example that you should be able to copy/paste and run, if you pull latest from GitHub on the dev branch.
[TestCase(FluentAutomation.SeleniumWebDriver.Browser.InternetExplorer)]
[TestCase(FluentAutomation.SeleniumWebDriver.Browser.Chrome)]
public void CartTest(FluentAutomation.SeleniumWebDriver.Browser browser)
{
FluentAutomation.SeleniumWebDriver.Bootstrap(browser);
I.Open("http://automation.apphb.com/forms");
I.Select("Motorcycles").From(".liveExample tr select:eq(0)"); // Select by value/text
I.Select(2).From(".liveExample tr select:eq(1)"); // Select by index
I.Enter(6).In(".liveExample td.quantity input:eq(0)");
I.Expect.Text("$197.70").In(".liveExample tr span:eq(1)");
// add second product
I.Click(".liveExample button:eq(0)");
I.Select(1).From(".liveExample tr select:eq(2)");
I.Select(4).From(".liveExample tr select:eq(3)");
I.Enter(8).In(".liveExample td.quantity input:eq(1)");
I.Expect.Text("$788.64").In(".liveExample tr span:eq(3)");
// validate totals
I.Expect.Text("$986.34").In("p.grandTotal span");
// remove first product
I.Click(".liveExample a:eq(0)");
// validate new total
I.WaitUntil(() => I.Expect.Text("$788.64").In("p.grandTotal span"));
I.Dispose();
}
It should find its way to NuGet in the next release which I'm hoping happens this week.
NuGet v2.0: Currently only one call to Bootstrap is supported per test. In v1 we had built-in support for running the same test against all the browsers supported by a provider but found that users preferred to split it out into multiple tests.
The way I manage it with v2 is to have a 'Base' TestClass that has the TestMethods in it. I then extend that once per browser I want to target, and override the constructor to call the appropriate Bootstrap method.
A bit more verbose but very easy to manage.
I have the following PHP code;
<?php
component_customer_init();
component_customer_go();
function component_customer_init()
{
$customer = Customer::getInstance();
$customer->set(1);
}
function component_customer_go()
{
$customer = Customer::getInstance();
$customer->get();
}
class Customer
{
public $id;
static $class = false;
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
else
{
return self::$class;
}
}
public function set($id)
{
$this->id = $id;
}
public function get()
{
print $this->id;
}
}
?>
I get the following error;
Fatal error: Call to a member function set() on a non-object in /.../classes/customer.php on line 9
Can anyone tell me why I get this error? I know this code might look strange, but it's based on a component system that I'm writing for a CMS. The aim is to be able to replace HTML tags in the template e.g.;
<!-- component:customer-login -->
with;
<?php component_customer_login(); ?>
I also need to call pre-render methods of the "Customer" class to validate forms before output is made etc.
If anyone can think of a better way, please let me know but in the first instance, I'd like to know why I get the "Fatal error" mentioned above.
Well, I think your Customer::getInstance() method is flawed. It should look like this:
...
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
return self::$class; // ADDED!!
}
else
{
return self::$class;
}
}
....
In the if(self::$class == false) branch you are creating the instance of the class, but you dont return it.
You could also rewrite it as such:
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
return self::$class;
}
To make it a bit shorter.
DRY: Don't Repeat Yourself
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
return self::$class;
}
And for Sinlgetons it is also important to prevent __clone() from being used. Making it private should solve that problem:
private function __clone() {}