October 23, 2024
In the world of software development, you’ll often come across two contrasting principles when writing tests: WET (Write Everything Twice) and DRY (Don’t Repeat Yourself). These principles guide developers on how to structure their code to enhance readability, maintainability, and efficiency.
The DRY principle emphasizes the importance of avoiding repetition in your codebase, ensuring that each piece of logic is defined only once. You can dive deeper into the DRY principle in this Don’t Repeat Yourself Wikipedia article.
Understanding the difference between WET and DRY testing is crucial because I've often seen how quickly things can become difficult to manage. When tests aren't structured thoughtfully from the start, it can be challenging and time-consuming to refactor them later on.
WET testing refers to a testing style where some code repetition is allowed in the test cases. This is often seen as a trade-off for readability, especially when the tests are simple.
describe('Login Tests - WET', () => {
test('should allow user to login with valid credentials', async () => {
await page.goto('https://example.com/login');
await page.fill('input[name="username"]', 'user1');
await page.fill('input[name="password"]', 'password1');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('https://example.com/dashboard');
});
test('should show an error with invalid credentials', async () => {
await page.goto('https://example.com/login');
await page.fill('input[name="username"]', 'user1');
await page.fill('input[name="password"]', 'wrongpassword');
await page.click('button[type="submit"]');
await expect(page).toHaveText('Invalid username or password');
});
});
In this WET test, the code for filling out the login form and clicking the submit button is repeated in both test cases.
DRY is a principle where you aim to avoid repeating code. In the context of tests, this means abstracting shared logic into reusable functions or setup steps, reducing duplication.
describe('Login Tests - DRY', () => {
const login = async (username, password) => {
await page.goto('https://example.com/login');
await page.fill('input[name="username"]', username);
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
};
test('should allow user to login with valid credentials', async () => {
await login('user1', 'password1');
await expect(page).toHaveURL('https://example.com/dashboard');
});
test('should show an error with invalid credentials', async () => {
await login('user1', 'wrongpassword');
await expect(page).toHaveText('Invalid username or password');
});
});
In this DRY test, the common login functionality has been abstracted into the login function, avoiding repetition.
This example may be simple, but the goal of this blog is to provide a clear understanding of the pros and cons of WET and DRY approaches, helping you decide which is more suitable for your testing needs.
From my personal experience, I highly recommend having a clear understanding of the requirements before you start writing tests. On a broader level, if your goal is simply to create a few test cases, avoid creating variables for selectors that you only use once or twice. However, if you're building a larger suite of tests with similar, critical application flows, the DRY principle can be invaluable. Using DRY will make your test suite easier to maintain in the long run.
While the DRY principle is generally recommended, it's important to balance readability and maintainability. In some cases, writing slightly repetitive tests (WET) can enhance clarity, especially when the tests are short and focused. However, for larger and more complex test suites, applying DRY leads to cleaner, more maintainable code.
Ultimately, the goal should be test clarity and maintainability, so choose the approach that best suits your project's scale and complexity