4

I've been hired in a company, where I'm the only QA Engineer in the team. One of my goals is to automate UI tests by using Selenium Webdriver. The project, that I'm working on, is complex form and every steps are related to each other. I mean it is not possible to pass through the 2nd step of the form without finishing the 1st one and so on. Let's say that the form has 12 pages.

I already automated the project but I want to make my code better and accomplish a wide-known patterns and good practices. One of the popular practice is to keep tests "Small, Atomic, Autonomous". I was reading a lot about it, but I don't know how to implement it into my project. My tests can't be autonomous because it is technically impossible, I guess. My questions are: does anyone have an experience in projects like mine and how did you accomplished described rule above? Should I follow these rules, good practices and patterns in every case?

  • Are you using BDD tools such as Cucumber or JBehave? If not, I recommend going that route. – Bill Hileman Dec 18 '17 at 20:18
  • Not yet, but I'm going to use Cucumber. I just finished write a test scenario which is passing through the whole form by happy path. I used page object pattern and that's all – mathsicist Dec 18 '17 at 20:20
  • You're on the right track. Page Objects would have been my next suggestion. – Bill Hileman Dec 18 '17 at 20:22
3

"Small, atomic, autonomous" is better fit for unit test, not for UI/End-to-end tests like you are writing.

Of course you should try to make your test to be small and autonomous. But very often you cannot, because your goal is not to test small part of the project, but complete interaction of the user with the system, often in several variants, depending of the business logic.

In example, one of my test creates several versions of the product, then logs to the system as several different users with different security settings, check the presence/absence of the created products in dozen of pages, and present/enabled/disabled for buttons according to business rules, BEFORE some features of the products are deleted (according to rules), and then checking same pages again.

Whole test runs for more than 15 minutes, and it as fast as webdriver can fill the form and browser can load the webpages. For many purists, that would not be "small" test - but it is just one test, checking for the business logic for the different versions of the same product, and how they are handled by several webpages. Functionality was implemented as one feature (working with many existing pages).

What helps this test to be manageable is good old-fashioned object design: using pageobject for all pages, and using data-driven design, where test data define different variants of the product, and test interprets the data, so definition of the data (just few lines, "small and autonomous" by your definition) describes exactly what will happen in each step of the test.

You can use "autonomous" to run simpler (and quicker running) tests before more complex: in my example above, creating a single variant of a product before creating whole batch and testing all users and pages. But in e2e testing, your test will never be small and autonomous, except trivial cases like check if you can login.

| improve this answer | |
2

I subscribe to FIRST - Fast, Independent, Repeatable, Self-Validating, and Timely. Tests need to run as fast as possible (no implicit waits or sleeps), run by itself with the same expected results, can be ran back-to-back with itself (before and after deployments), end with an Assert (I also follow Arrange, Act, Assert), and run as part of CI with reliable and easily parse-able results.

You want to test everything as simply as possible but often one function relies on another. For our example, we need to log in and then update our profile. So we can't test the 'Profile' page without first hitting the 'Log In' page. Well, we already have a small, happy-path test for logins that might look like:

[Test]
public void Login_HappyPath()
{
    // Arrange: Get necessary values
    User user = User.GetUser("admin");
    // Act: Login using valid credentails
    App.LoginPage.LoginAs(user);
    // Assert: Login was successful
    Assert.IsTrue(App.GlobalToolbar.UserProfileImage.Displayed);
}

With each test that needs to login first, we just call the login method and move on with life. If the login doesn't work for whatever reason, then we'll never be able to test the 'Profile' page as the test(s) would likely throw an exception trying to get there.

Things start getting a little more complicated with something like CRUD (Create, Read, Update, Delete) operations. With the Read, we need a record as a prerequisite. There are many ways you can get a record for testing. You could seed the data in the 'Arrange' part of your test or you could have specific read-only test records in a database. The options go on.

[Test]
public void Read_HappyPath()
{
    // Arrange: Get the ID for the test record
    string recordId = TestUtils.GetTestRecord("read_happypath");
    // Act: Request the record by ID
    JObject recordJsonObject = App.RecordsPage.RequestById(recordId);
    // Assert: The record 
    Assert.AreEqual(recordId, recordJsonObject["id"].ToString()); 
}

Takeaway: First, test everything as small as you can ('unit' tests). Then start chaining common methods into other ('integration') tests. Don't be surprised when an 'integration' test fails as you'll likely be able to track it back to a 'unit' test. Do take care in test design so that one test can run by itself. Note: I am using the terms 'unit' and 'integration' loosely here.

| improve this answer | |
  • FIRST and CRUD seem as a very useful rules, but I don't see it in my project. – mathsicist Dec 20 '17 at 11:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.