Questions From Testers - How To Better Structure My Testing Career

I receive from time to time questions from testers about problems they have. 

Most questions are about test automation but sometimes they are not. 

I answered recently an email from a tester that lives on San Francisco, USA who was wondering how to better structure her testing career. 



QUESTION


I am a QA with 5 years of experience but mostly manual testing. 

I am now comfortable with all the testing and how it's done and it comes easy. 

But the technical parts are challenging in part because I don't have a computer science degree. 

I took a certification course for QA in the local certified training facility. 

I was able to land a few really good contracts and have been successful. 

I am right now working for a company and I am not happy with their environment. 

So I have been looking for other positions and I noticed a trend that is so obvious. 

Automation is what everyone wants. 

I would like to get some advice on how to get started. 

I don't know any object oriented languages but would like to start with Java or Python. 

But in general i need advice on how to better structure my career progression in terms of knowledge that is necessary. 

If you could be so kind as to respond to this email I would be extremely appreciative. 

Look forward to you soon. 

Sincerely, 




ANSWER

Hi,

My opinion on career improvement is this. 

The future of the "pure" manual tester is slowly disappearing. 

Why am I saying this?

The most important tasks for a typical manual tester are to
  • create test cases
  • execute test cases
  • log bugs

So manual testers create test cases. 

A business analyst is probably better at creating test cases than a tester. 

Business analysts can create business requirements too so hiring a business analyst may add more value than hiring a tester. 



Testers execute also test cases and log issues. 

Any business user can do this as well as a tester. 

Business users can also do their business tasks. 



So why use manual testers if their tasks can be absorbed by other users?


For these reasons, companies hire less and less manual testers because they can find easily replacements: 
  1. business analysts
  2. subject matter experts (business users)
  3. developers 

Since many manual testers do not have high testing skills or good testing education, it is not difficult to replace them with non-testing resources. 


You may have heard about things like "testing is dead" or "testers are dead"

They try to summarize the fact that just being a tester is insufficient

"Testers are dead" can actually be replaced with "Unprofessional testers are dead".

Professional manual testers will continue to exist.


To keep their job or find a new one, manual testers have to develop high testing skills on 
  • scripted manual testing 
  • exploratory testing 
  • fast learning 
  • testing with no requirements 
  • high creativity 
  • mind mapping 
The competition for these jobs is very high in most cases. 

All manual testers, including the great ones, will be in a crowd of average manual testers.



The future belongs to the technical tester 



You can call him/her also software tester in development. 

This is a tester with many diverse skills about 
  • programming (Java)
  • how applications and systems work 
  • performance testing 
  • API testing
The more of these skills you have, 
The easier to find better, more interesting, challenging and paid better jobs. 


How do you get to be a technical tester? 

Start learning new things every day

Learning new things, every day, is the key. 

A few hours of learning per day will move you fast towards your goal. 

2 hours per day means 60 hours per month. 
This is 2.5 days per month dedicated only to learning new things. 
Which is almost a month per year. 




There are 2 approaches that you can take for your learning:
  • fill gaps
  • build knowledge step by step

Filling gaps works when you notice holes in your knowledge about a specific topic.

As soon as you find a gap, try filling it by reading around the topic.

When you want to learn new things (like programming), the building step-by-step works better.

You have in this case an empty space that needs to be populated with new knowledge.

Building a house is a good metaphor.

You build first a wall, brick by brick.

When the wall is up, check that it is solid and built correctly.

If the first wall is not good enough, do it again.

Then, continue with the second wall, third, until you have a full room.

The first room is difficult to build.

The second is far easier than building the first.

Third room is easier than building the second and so on.



There are 2 options of improving your education
  1. do it by yourself 
  2. with a coach 

When you do it by yourself, getting started is difficult. 

Learning will be challenging until you have a foundation of knowledge built. 

The challenges come from not knowing 
  • what to read 
  • how much to learn 
  • what is the progression from one topic to another 
  • what is useful
  • what is not useful 

With a coach, all these are solved and you just need to put in the time and effort. 

In my opinion, learning a programming language and test automation is the best start for becoming a technical tester. 

In this process, you will get exposed to many other things that you need to know such as 
  • XPATH
  • HTML
  • CSS
  • how browsers work
  • how HTTP works 


If you want to learn Java and Selenium WebDriver, I can help you with that. 

I can help you as well with 
  1. creating practical test automation experience to use in your resume
  2. getting noticed by companies and recruiters
  3. getting ready for test automation interviewing
  4. feedback on your tester resume

I have been helping manual testers do this for about 2 years now. 

It is not free but what you get is private lessons focused only on you and your learning. 

Hope this helps.



Have a question about test automation or testing in general?

Email me at alex@alexsiminiuc.com.

Selenium WebDriver Interview Test

Practice tests are very useful for testing the level of our knowledge and skills.

We get useful feedback through them about what we can do with the new learned skills at a certain moment.

But practice tests show us as well what we don't know yet but should know.

What follows is a real interview test for Selenium WebDriver and Java.


1. Open the www.ia.ca in the Chrome browser





2. Click LOANS

3. Click the Mortgages link




4. Click the Calculate Your Payments button




5. Move the Purchase Price Slider to the right

6. Validate that the Purchase Price Slider movement works




7. Change the Purchase Price to 500 000 using the + button of the slider




8. Change the Down Payment to 50 000 using the + button of the slider




9. Select 15 years for Amortization




10. Select Weekly for Payment Frequency




11. Change the Interest Rate to 5%




12. Click the Calculate button




13. Verify that the weekly payments value is 836.75




Good luck doing the exercise!

If you are interested in feedback, post your code in the comments section.





9 Easy TestNG Features You Can Learn Today That Will Improve Your Test Automation




The TestNG unit testing framework has many easy-to-implement features that will make you test automation easier:
  1. Dependent scripts
  2. Soft assertions
  3. Skipped tests
  4. Execute tests multiple times
  5. Before/After Method, Class, Test, Group, Suite
  6. Tests with parameters
  7. Run tests in parallel
  8. Reports
  9. Run failed tests
Most of these features do not exist in JUNIT.



INSTALL TEST NG



First, lets see how Test NG can be installed:

  • In Eclipse, open Eclipse Marketplace (Help menu --> Eclipse MarketPlace)



  • Select the Popular tab in the Eclipse MarketPlace window
  • Find the Test NG for Eclipse Plugin
  • Install it




  • Add Test NG to the project:
    • Right click on the project name and select Properties



    • In the Properties page, select the Java Build Path tab, select Libraries and click the Add Library button


    • Select Test NG and click Finish











It is a good practice to have only independent test scripts when working on test automation.

In some cases, however, it is useful to have the ability of making test scripts dependent of other scripts.

For example, let's assume that we have a number of test scripts that all depend on a database being available.

If the database is not available, all test scripts fail.

To avoid having all test scripts failing, we could create a test script that checks the database status.

Then, we can make all other test scripts depend on the "check database status" script.

The result will be that, if the database is not available, the other test scripts will not fail but be ignored.


It is not possible to have dependent scripts in JUNIT.


Test NG allows creating dependent tests using methods and groups.


TEST SCRIPTS THAT DEPEND ON METHODS

package tests;

import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.Test;

public class OrderedCheckOutTests {

@Test (dependsOnMethods = "addProductToCart")
public void checkOutAndPay()
{

System.out.println("3. checkOutAndPay Completed ");  

}

@Test (dependsOnMethods = "searchForProduct")
public void addProductToCart()
{

System.out.println("2. addProductToCart Completed");

}

@Test
public void searchForProduct()
{

System.out.println("1. searchForProduct Completed");

assertTrue(false);

}

}

When executing the test class, the test scripts run in the following order:
  1. searchForProduct()
  2. addProductToCart() (depends on searchForProduct so searchForProduct runs first)
  3. checkOutAndPay() (depends on addProductToCart so addProductToCart runs first)





TEST SCRIPTS THAT DEPEND ON GROUPS

package tests;

import org.testng.annotations.Test;

public class OrderedCheckOutTests2 {

@Test (dependsOnGroups = "search")
public void checkOutAndPay()
{

System.out.println("3. checkOutAndPay Completed ");  

}

@Test (groups = "search")
public void addProductToCart()
{

System.out.println("2. addProductToCart Completed");

}

@Test (groups = "search")
public void searchForProduct()
{

System.out.println("1. searchForProduct Completed");  

}

}


When executing the test class, the test scripts run in the following order:
  1. searchForProduct()
  2. addProductToCart() (the first 2 scripts can run in any order)
  3. checkOutAndPay() (depends on searchForProduct and addProductToCart so these 2 scripts run first)
















An example will help explaining how soft assertions work.

We are working on automating testing for a user registration page.






There are multiple fields in the page for username, password, first name, last name, email, etc.

The test script checks if each field value is valid so it has an assertion for each field.

If multiple fields have invalid values, the assertion for the first invalid field fails and the remaining assertions are ignored.

It would be nice if we would have the ability to run assertions for all fields and then 
  • fail the script if at least one assertion fails
  • pass the script if all assertions pass
This is what soft assertions do:



package tests;

import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;

public class SoftAssertions {

SoftAssert softAssertion = new SoftAssert();

@Test
public void testHardAssert1()
{

assertTrue(false);

} 

@Test
public void testHardAssert2()
{

assertTrue(false);

assertTrue(false);

assertTrue(true);

}

@Test 
public void testSoftAssert2()
{

softAssertion.assertTrue(false);

softAssertion.assertEquals(1, 2);

softAssertion.assertEquals(true, false);

softAssertion.assertAll();

}

} 


When running the test class, we get the following results:
  1. testHardAssert1() fails
  2. testHardAssert2() fails (only the first assertion is executed)
  3. testSoftAssert2() fails (all 3 assertions are executed)


Soft assertions work as containers for normal assertions.

If any of the child assertions fails, the soft assertion fails after all child assertions run.

If all child assertions pass, the soft assertion passes as well.

















Lets go back to the example where multiple test scripts depend on a database being available.

Lets assume that each test script checks first if the database is available.

If the database is not available, it would be nice to flag the tests as skipped instead of failed.

The SkipException from Test NG can be used for skipping tests:



package tests;

import org.testng.SkipException;
import org.testng.annotations.Test;

public class SkippedTests {

@Test
public void checkOutAndPay()
{

System.out.println("3. checkOutAndPay Completed ");

}

@Test
public void addProductToCart()
{

System.out.println("2. addProductToCart Completed");

throw new SkipException("Skipping this exception");

}

@Test
public void searchForProduct()
{

System.out.println("1. searchForProduct Completed"); 

if (1 != 2)
throw new SkipException("Skipping this exception");

}

}














If a test script needs to be executed multiple times, the @Test (invocationCount = 10) annotation can be used as shown in the example below.


package tests;

import org.testng.annotations.Test;

@Test (groups = "regression")
public class TestsExecutedMultipleTimes {

@Test (invocationCount = 10)
public void test1()
{

}

@Test
public void test2()
{

}

}















In Test NG, the following components are being used:
  • Method
  • Suite: made of one or more tests
  • Test: made of one or more classes
  • Class: made of one or more methods
  • Group: made of one or more methods of different classes

Test NG has Before and After annotations for all these components (compared with JUNIT which has them only for methods, classes and suites):
  • @BeforeSuite / @AfterSuite — before a suite starts / after all the test methods in a certain suite have been run
  • @BeforeTest / @AfterTest — before a test starts / after all the test methods in a certain test have been run (remember that a test is made of
  • @BeforeClass / @AfterClass — before a test class starts / after all the test methods in a certain class have been run
  • @BeforeMethod / @AfterMethod — before a test method is run / after a test method has been run
  • @BeforeGroups / @AfterGroups — before any test method in a given group is run / after all the test methods in a given group have been run
















Tests with parameters exist in JUNIT as well.

Test NG adds more functionality for them.

The parameter values can come from
  • the Test NG XML file
    • the test script uses  @Parameters ({ "parameter1", "parameter2"}) for specifying the parameter names
  • multiple data providers; this is an improvement compared to JUNIT where only 1 provider is possible
    • the test script uses @Test(dataProvider = "provider1") for specifying the data provider that it needs
    • the test script can have parameters like any other method (not possible in JUNIT)


TEST NG XML FILE




<suite name="Suite1" verbose="1" >
<parameter name="parameter1" value="value 1" />
<parameter name="parameter2" value="value 2" />
</suite>






package tests;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class TestsWithParameters {

@DataProvider(name = "provider1")
public Object[][] provider1() {

return new Object[][] {
{ "provider1_aaa", "provider1_bbb" },
{ "provider1_ccc", "provider1_ddd" },
{ "provider1_eee", "provider1_fff" },
{ "provider1_ggg", "provider1_hhh"},
{ "provider1_iii", "provider1_jjj" },

};

}

@DataProvider(name = "provider2")
public Object[][] provider2() {

return new Object[][] {
{ "provider2_aaa", "provider2_bbb" },
{ "provider2_ccc", "provider2_ddd" },
{ "provider2_eee", "provider2_fff" },
{ "provider2_ggg", "provider2_hhh"},
{ "provider2_iii", "provider2_jjj" },

};

}

@Parameters ({ "parameter1", "parameter2"})
@Test
public void displayParameterValuesFromFile(String p1, String p2) {

System.out.println(p1 + " , " + p2);

}

@Test(dataProvider = "provider1")
public void displayParameterValuesFromDataProvider1(String p1, String p2) {

System.out.println(p1 + " , " + p2);

}

@Test(dataProvider = "provider2")
public void displayParameterValuesFromDataProvider2(String p1, String p2) {

System.out.println(p1 + " , " + p2);

}

}














When time is an issue and the execution of the test suite is slow, running test scripts in parallel will help.

Running test scripts in parallel in Test NG is simpler than doing it in JUNIT.

And it does not require using Maven or Maven SureFire.

You just need to specify values for the invocation count and the threadPoolSize parameters.



package tests;

import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.Test;

public class ParallelTests {

@Test(invocationCount = 5, threadPoolSize = 10)
public void test1()
{

assertTrue (true);

}

@Test(invocationCount = 5, threadPoolSize = 10)
public void test2()
{

assertTrue (true);

}

@Test(invocationCount = 5, threadPoolSize = 10)
public void test3()
{

assertTrue (true);

}

@Test(invocationCount = 5, threadPoolSize = 10)
public void test4()
{

assertTrue (true);

}

@Test(invocationCount = 5, threadPoolSize = 10)
public void test5()
{

assertTrue (true);

}

}












Test NG allows running the failed test scripts without making any changes to the test annotations.

This is so much easier than in JUNIT where you need to ignore the passing tests to be able to run the failed ones.

An example will make this clear.

Lets start with a simple class with 6 test scripts.



import static org.testng.AssertJUnit.assertTrue;

import org.testng.Assert;
import org.testng.annotations.Test;
public class class1 {
 
@Test
public void test1()
{
Assert.assertTrue(true);
}

@Test
public void test2()
{
Assert.assertTrue(true);
}

@Test
public void test3()
{
Assert.assertTrue(true);
}

@Test
public void test4()
{
Assert.assertTrue(true);
}

@Test
public void test5()
{
Assert.assertTrue(true);
}

@Test
public void test6()
{
Assert.assertTrue(true);
}

}



Running the class makes all test scripts pass:

[TestNG] Running: C:\Users\home\AppData\Local\Temp\testng-eclipse--1476802653\testng-customsuite.xml

PASSED: test1
PASSED: test2
PASSED: test3
PASSED: test4
PASSED: test5
PASSED: test6

==============================================

Default test Tests run: 6, Failures: 0, Skips: 0 ==============================================

Default suite Total tests run: 6, Failures: 0, Skips: 0

==============================================



Lets make 2 of the test scripts fail (test2 and test4).


import static org.testng.AssertJUnit.assertTrue;

import org.testng.Assert;
import org.testng.annotations.Test;
public class class1 {
 
@Test
public void test1()
{
Assert.assertTrue(true);
}

@Test
public void test2()
{
Assert.assertTrue(false);
}

@Test
public void test3()
{
Assert.assertTrue(true);
}

@Test
public void test4()
{
Assert.assertTrue(false);
}

@Test
public void test5()
{
Assert.assertTrue(true);
}

@Test
public void test6()
{
Assert.assertTrue(true);
}

}



Re-running the class shows 2 test scripts failing:

=============================================== 

 Default test Tests run: 6, Failures: 2, Skips: 0 

=============================================== 

Default suite Total tests run: 6, Failures: 2, Skips: 0 

===============================================

To re-run only the failed scripts, we need to

  • fix the errors from the failed tests
  • select the Results Of Running Suite Tab


  • click the Run Failed Test Button




The failed tests only are being re-executed.















JUNIT does not have any default reporting functionality.

Reports can be generated in different ways


Test NG has a simple report built in.

To see it, follow the next steps:

  • click the Results Of Running Suite Tab
  • click the Open Test NG Report button



The report is displayed in a new tab of the Eclipse editor.

Customized information can be added to the Reporter Output section using the Reporter.log(text) method:




Do Cross Browser Test Automation Like A PRO - Part 2


Part 1 showed how to run a test script in a local browser.

In Part 2, we  change the code so that the test script runs in multiple local browsers.

See below how each class changes.





TEST CLASS


The test class needs the following changes:
  • @RunWith(value = Parameterized.class) annotation is added before the class so that the class can have test scripts with parameters
  • browserName member is added as parameter of the test script; it uses the @Parameter annotation
  • the public static Collection object data () specifies the values that the browserName parameter can take
  • the Driver.initialize(browserName) method uses the browserName parameter 


package TESTS;
import static org.junit.Assert.*;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.Collection;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import FRAMEWORK.Driver;
import FRAMEWORK.HomePage;
import FRAMEWORK.ResultsPage;

@RunWith(value = Parameterized.class)
public class TestScripts_LocalBrowsers {                  

@Parameter
public String browserName;   

@Parameters
public static Collection object data () {
     
Object[][] data = {{"Firefox"}, {"Chrome"}, {"InternetExplorer"}};

return Arrays.asList(data);   

} 

@Before
public void setUp() throws MalformedURLException 
{   

Driver.initialize(browserName); 

}

@After
public void tearDown()
{   

Driver.getDriver().quit();  

}

@Test
public void testResultsInfo() throws InterruptedException
{        

ResultsPage results = (new HomePage()).search();    
     
assertTrue(results.isKeywordDisplayed() == true);                     

assertTrue(results.resultsCount() > 0);             

}   

}



DRIVER CLASS


The driver class needs the following changes:
  • a new initialize(browser) method is added; the new method uses a parameter for the browserName
  • the initialize(browser) method creates the browser driver that corresponds to the browser name



package FRAMEWORK;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public class Driver { 

private static WebDriver driver;  

public static WebDriver getDriver()
{

return driver;

}

public static void initialize() 
{      

driver = getFirefoxDriver();

}

public static void quit() 
{      

getDriver().quit();

}

public static void initialize(String browser) throws MalformedURLException
{      

if (browser.equalsIgnoreCase("Chrome")) 
{

driver = getChromeDriver();
return;

}

if (browser.equalsIgnoreCase("InternetExplorer"))
{

driver = getInternetExplorerDriver();
return;

}

if (browser.equalsIgnoreCase("Firefox"))
{

driver = getFirefoxDriver();
return;

}

throw new NoSuchElementException("Invalid browser name");

}   

private static WebDriver getChromeDriver()
{

System.setProperty("webdriver.chrome.driver", 
"C:\\Selenium\\BrowserDrivers\\chromedriver.exe");

return  new ChromeDriver(); 

}

private static WebDriver getFirefoxDriver()
{    

return new FirefoxDriver();

}

private static WebDriver getInternetExplorerDriver()
{  

System.setProperty("webdriver.ie.driver", 
"C:\\Selenium\\BrowserDrivers\\IEDriverServer.exe");

return  new InternetExplorerDriver();     

}      

}



HOME PAGE CLASS


The home page class has only one change.

It uses By locators instead of String locators.

It uses By.id and By.name when possible.


package FRAMEWORK;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class HomePage {

By searchMenuItemLocator = By.id("search-cta");
By searchTextBoxLocator  = By.id("searchinput");
By searchButtonLocator   = By.name("simplesearch");  

WebDriver driver; 
WebDriverWait wait;               

public HomePage()
{  

this.driver = Driver.getDriver();
wait = new WebDriverWait(driver, 10);

driver.get(Settings.siteUrl);

wait.until(ExpectedConditions.titleIs(Settings.homePageTitle));

}   

public ResultsPage search()
{

wait.until(ExpectedConditions.
elementToBeClickable(searchMenuItemLocator)).
click();

wait.until(ExpectedConditions.
elementToBeClickable(searchTextBoxLocator)).
sendKeys(Settings.keyword);

wait.until(ExpectedConditions.
elementToBeClickable(searchButtonLocator)).
click();    

return new ResultsPage();  

}     

}



RESULTS PAGE CLASS


The results page class has only one change.

It uses By locators instead of String locators.

It uses By.id and By.name when possible.


package FRAMEWORK;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class ResultsPage { 

By searchKeywordLocator = By.xpath("//h2[@class='product-search']/a"); 
By resultsCountLocator  = By.xpath("(//div[@class='resultshits'])[1]");

WebDriver driver; 
WebDriverWait wait; 

public ResultsPage()
{ 

this.driver = Driver.getDriver();
wait = new WebDriverWait(driver, 10);

wait.until(ExpectedConditions.titleIs(Settings.resultsPageTitle));

}    

public Boolean isKeywordDisplayed()
{ 

return wait.until(ExpectedConditions.
visibilityOfElementLocated(searchKeywordLocator)).
isDisplayed(); 

}    

public int resultsCount()
{     

WebElement resultCountElement = wait.until(ExpectedConditions.
visibilityOfElementLocated(resultsCountLocator));

return Integer.parseInt(extractValue(resultCountElement.getText(), Settings.resultsCountRegEx, 3)); 

}

public String extractValue(String textValue, String regEx, int position)
{ 

String result = "";

Pattern pattern = Pattern.compile(regEx);

Matcher matcher = pattern.matcher(textValue);

if (matcher.find())

result = matcher.group(position); 

return result;

} 

}


Part 3 will change the code more so that the test script runs in a cloud Selenium Grid.


If you have any questions about how the code works or any difficulties running it, please post them in the Comments section.