Shortcut to seniority

Home
Go to main page

Section level: Junior
A journey into the programming realm

Section level: Intermediate
The point of no return

Section level: Senior
Leaping into the unknown
Go to main page
A journey into the programming realm
The point of no return
Leaping into the unknown
Testing is the process of testing the software for defects, through manual or automated means.
White box testing is a a technique in which the testers require knowledge of how the software is implemented and how it works.
Black box testing is a technique in which the QA team has no knowledge of how the software is implemented.
The QA team is testing to see that the behavior expected in the specification is respected in the system by checking how the software reacts.
Smoke testing is a technique usually performed when a new build is available. It consists of a very limited test cases in order to ensure that the important features are working fine and that they are ready to proceed further with other testing types.
The term smoke testing originates from the hardware testing, where a device when first switched on is tested for the smoke or fire from its components. This ensures that the hardware’s basic components are working fine and no serious failures are found.
Regression testing is a technique in which the QA checks to see if new defects were introduced in previously existing functionality. Usually, if regressions are found, the build is rejected.
User Acceptance Testing (UAT) is performed by the user / customer. The goal of UAT is to establish confidence in the system, by validating the specifications received from the customer.
Performance testing is asserting that the system meets the performance criteria. It can also measure what part of the system or workload causes the system to perform poorly.
Performance measurements can be done through calculating the average response time of the server after hundreds or thousands of requests were processed.
Security testing verifies whether the application is secured or not, by asserting whether it is vulnerable to attacks, if anyone can hack the system or login without authorization, etc. The security testing also checks whether there’s any information leakage by making sure the software encryption is used for all communication, using secure connection for sensitive data, not logging in plain text confidential data, check what errors are being returned to the user and whether they expose any information about the system.
The six basic security concepts that need to be covered by security testing are: confidentiality, integrity, authentication, availability, authorization and non-repudiation.
Negative testing is a testing type that checks if a system responds properly under unexpected conditions, such as wrong data type, or hacking attacks.
A test plan refers to planning the activities that must be performed during testing to guarantee that the testing was properly executed.
A test case is a document which contains a set of actions that are performed on the software to verify the functionality of a feature. The test case should contain the test data, prerequisites, and expected results.
A test suite is a collection of test cases that are used together to test the functionality of a specific feature.
Manual testing is the process of manually testing the software for defects. The tester plays the role of an end user and checks the features, making sure that no bugs are left out. The testing usually follows a test plan, which leads them through a set of test cases.
Testers are your friends, not your enemies.
If you think the testers make you look bad, when returning your commit, or your software release, it is so much better to have it returned by the system test instead of receiving an official complaint by the customer.
When the software is bad, the system test should and must return it as bad. It’s not their fault the software is bad.
Take responsibility over your actions.
If you’re building a software for a rocket company, wouldn’t you prefer to have it returned, instead of it exploding mid-air and have the client lose tens of millions of dollars?
When programmers and testers start to collaborate, there will be less time spent sending the bugs back and forth, and more time to develop good software.
Programmers can provide testers informations that help with better test coverage, or help them avoid time on unnecessary tests.
The testers can provide programmers with feedback regarding what works, what doesn’t, what’s expected and what happens instead, or what doesn’t look so great.
Manual testing is not that great, having a lot of issues that could be avoided, such as:
A unit test is a method in which individual units of source code (functions, classes, interfaces) are tested to assure that they work properly. Usually, unit tests are performed on functions and tests that the input is properly interpreted and that the output is as expected by the programmer.
When you write unit tests, you should add some description for it to let others know what is the use case that you're testing.
For each test, you should provide the usage scenario - the preconditions, the context, the expected results, and the postconditions to be verified.
When people see a suite of tests, they should be able to see and understand what causes the software to behave differently.
The test name should describe the particular usage scenario, so that the test coverage can easily be scanned through function names. Unit testing is the first line of defense against bugs, and as a developer, it is your job to write maintainable, testable code.
Automated testing is the process of automatically testing the software for defects. This usually involves a tool or programming language that automates the execution of a test case, emulating a user interacting with the software.
There are a couple of reasons why automated testing should be preferred over manual testing:
There are specific tests that should be automated, and the most relevant are those that are deterministic, that are hard to test manually or that are need to run more than once, and that don’t need human interaction. Other perfect examples of tests that should be automated are those that needs to run against different data sets or against multiple builds and browsers, or those that are used for load/stress testing.
In theory, the more repetitive the execution is, the better candidate a test is for test automation.
A test automation framework is a common set of tools, guidelines, and principles for your tests.
The test automation process should follow a six-step cyclical process, and their phases are detailed below.
The metrics for test automation should be used as a quick way to monitor the progress of the team.
Mean time to diagnosis (MTD) – How long does it take you to debug a failing automated test? If there’s a high MTD, then the quality of the automated test code is not very high.
Bugs found by Automation – Helpful to determine how much value the automation efforts is bringing to the project.
Flaky Rate – Should be zero ideally, and it’s a good indicator to know if the automated tests are reliable or not.
Automated to Manual Ratio – The more manual tests you have, the longer it will take to tell if the application is ready for release. It also helps you measure how long the release efforts will take.
In order to verify code parts / components independently from the rest of the system, we need to create objects that look and behave like the real pieces, but they are not exactly like it. That is because although we want to test a single unit of code, in order to make it work, we often need other units.
Dependency injection is a technique in which we pass a dependency by injecting it into an object that would use it. (for example when we have class composition).
This is very useful when we want to inject a fake object in exchange of the real one, when we test the application.
This can be achieved in two ways:
Assuming we have the following class, we can see that it’s hard to create a unit test to validate the functionality for the work function, mainly because of the classes ‘’DependencyClass’ and ‘Worker’. Through Dependency injection, we can change the Worker object so we can test it. We can also test it without changing anything if the DependencyClass or Worker class have a state we can interogate while we call such functions, but that’s not the point here.
Constructor injection implies adding the dependency object through the class’s constructor.
Because the constructor is called only once for that object, we can be sure that the dependency was called and object was changed, and that it will not be changed further during the lifetime of the object.
class DependencyClass {
public:
DependencyClass() {}
void work() { myWorker.work(); }
private:
Worker myWorker;
}
class DependencyClass {
public:
DependencyClass(Worker* obj = new Worker) : myWorker(obj) {}
void work() { myWorker.work(); }
private:
Worker* myWorker;
}
Setter injection implies adding a setter method that receives the dependency object.
This is great when the dependency is optional – if you don’t want or need it, we simply don’t call the setter.
Another point in favor of Setters is that we can call them multiple times.
class DependencyClass {
public:
DependencyClass() { myWorker = new Worker; }
void setWorker(Worker* newWorker) { myWorker = newWorker;}
void work() { myWorker.work(); }
private:
Worker* myWorker;
}
Fakes are objects with working implementation, but different than the one from production, usually containing simplified code. Fakes are objects with limited capabilities, only for the purposes of testing, such as a fake web service or a fake database. The purpose of a fake is not to affect the behavior of the system under test, but to simplify the implementation of the test itself, by removing unnecessary or heavyweight dependencies).
An example would be when we want to test the functionality of storing / fetching data from a database, but using a simple collection instead.
Keep this in mind when you want to use Fakes: You test something that uses a fake, you are not testing the fake itself. The fake exists so that the thing that you are testing is not dependent on something external.
class DatabaseFake : public IDatabase {
public:
DatabaseFake() {
accounts["Account1"] = new AccountData("Account1", "Password1", "Fake Street 1");
accounts["Account2"] = new AccountData("Account2", "Password2", "Fake Street 2");
accounts["Account3"] = new AccountData("Account3", "Password3", "Fake Street 3");
}
AccountData& get(string account_name) { return accounts[account_name]; }
private:
unordered_map<string, AccountData> accounts;
};
Stubs are objects that hold some predefined data and use such data to answer calls.
Some of the functions are overwritten so they can validate the unit test, and probably the remaining ones are not working at all because we’re not interested in them. The purpose of a stub is to set up your test case.
An example would be when we want to test some code that interacts with a server, we can stub out the server with some hardcoded result.
class Stub {
public:
int getNumber(string value) {
if (value == "one") return 1;
if (value == "two") return 2;
return -1;
}
};
class UnitTest {
public:
void testStub() {
Stub* stubObject = new Stub;
int result = stubObject.getNumber("one");
assertEquals(1, result);
}
};
Mocks are objects that register the calls they receive, and through assertions, we can verify that all the expected actions were performed. The difference between mocks and stubs is that mocks are verifying the behavior, whereas stubs are verifying the state.
We have to use mocks when we don’t want to use production code, or when there’s no easy way to verify that the code was executed. This could happen because we can’t easily verify that the state has changed.
class Mock {
public:
bool call_one_performed = false;
bool call_two_performed = false;
int number_of_times_function_to_be_called = 3;
int number_of_times_function_was_called = 0;
int getNumber(string value) {
number_of_times_function_was_called++;
if (number_of_times_function_was_called > number_of_times_function_to_be_called) {
throw new AssertionException("getNumber was called too many times");
}
if (value == "one") {
if (call_two_performed)
throw new AssertionException("getNumber failed, value one, call_two_performed");
call_one_performed = true;
return 1;
}
if (value == "two") {
if (!call_one_performed)
throw new AssertionException("getNumber failed, value two, call_one_performed");
call_two_performed = true;
return 2;
}
return -1;
}
}
class UnitTest {
public:
void validate_mock_state(Mock* obj, int count, bool call_one, bool call_two) {
assertEquals(count, obj->number_of_times_function_was_called);
assertEquals(call_one, obj->call_one_performed);
assertEquals(call_two, obj->call_two_performed);
}
void testStub() {
Mock* mockObject = new Mock;
validate_mock_state(0, false, false);
mockObject->getNumber("one");
validate_mock_state(1, true, false);
mockObject->getNumber("two");
validate_mock_state(2, true, true);
}
}
Package diagram is a view displaying the coupled classes and their encapsulation into a group (similar to namespace).
Activity diagram is a view representing the flow from one activity to another, describing an operation within a system.
Reserve is a function that pre-allocates a specific memory size, to accommodate new data.
The act of exploiting a bug in order to get administrator access.
Composition refer to two classes (composite and component) in which one of them (composite) contain an instance of the other one (component), creating a ‘has a’ relationship. The composite object has ownership over the component, meaning that the lifetime of the component object starts and ends at the same time as the composite class. In simple terms: A class contains an instance of another class.