Unit testing a template class after refactoring

I am cleaning up my code by way of removing duplicates, and found two classes that were almost identical, out of 55 lines, only a single predicate in an if statement differed between them.

Both classes also had a suite of tests, which were almost identical copies as well.

The two original classes were FooApiAuthenticationProvider and BarApiAuthenticationProvider.

Below is my current refactored code using a template-pattern refactoring.

Template

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider
{
private readonly IClientRepository _clientRepository;
protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository)
{
if (clientRepository == null)
{
throw new ArgumentNullException("clientRepository");
}
_clientRepository = clientRepository;
}
public GenericPrincipal GetPrincipal(string username, string password)
{
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentNullException("username");
}
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("password");
}
var client = clientRepository.GetByUsername(username);
if (client == null && !DoesPasswordMatch(password, client))
{
return null;
}
return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID);
}
protected abstract bool DoesPasswordMatch(string password, Client client);
}
</code>
<code>public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider { private readonly IClientRepository _clientRepository; protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository) { if (clientRepository == null) { throw new ArgumentNullException("clientRepository"); } _clientRepository = clientRepository; } public GenericPrincipal GetPrincipal(string username, string password) { if (string.IsNullOrWhiteSpace(username)) { throw new ArgumentNullException("username"); } if (string.IsNullOrWhiteSpace(password)) { throw new ArgumentNullException("password"); } var client = clientRepository.GetByUsername(username); if (client == null && !DoesPasswordMatch(password, client)) { return null; } return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID); } protected abstract bool DoesPasswordMatch(string password, Client client); } </code>
public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider
{
    private readonly IClientRepository _clientRepository;

    protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository)
    {
        if (clientRepository == null)
        {
            throw new ArgumentNullException("clientRepository");
        }

        _clientRepository = clientRepository;
    }

    public GenericPrincipal GetPrincipal(string username, string password)
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            throw new ArgumentNullException("username");
        }

        if (string.IsNullOrWhiteSpace(password))
        {
            throw new ArgumentNullException("password");
        }

        var client = clientRepository.GetByUsername(username);

        if (client == null && !DoesPasswordMatch(password, client))
        {
            return null;
        }

        return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID);
    }

    protected abstract bool DoesPasswordMatch(string password, Client client);
}

FooApiAuthenticationProvider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
public FooApiAuthenticationProvider(IClientRepository clientRepository)
: base(clientRepostitory)
{
}
protected override bool DoesPasswordMatch(string password, Client client)
{
return client.FooApiPassword == password;
}
}
</code>
<code>public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate { public FooApiAuthenticationProvider(IClientRepository clientRepository) : base(clientRepostitory) { } protected override bool DoesPasswordMatch(string password, Client client) { return client.FooApiPassword == password; } } </code>
public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
    public FooApiAuthenticationProvider(IClientRepository clientRepository)
        : base(clientRepostitory)
    {
    }

    protected override bool DoesPasswordMatch(string password, Client client)
    {
        return client.FooApiPassword == password;
    }
}

BarApiAuthenticationProvider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
public BarApiAuthenticationProvider(IClientRepository clientRepository)
: base(clientRepostitory)
{
}
protected override bool DoesPasswordMatch(string password, Client client)
{
return client.DoesBarServicePasswordMatch(password);
}
}
</code>
<code>public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate { public BarApiAuthenticationProvider(IClientRepository clientRepository) : base(clientRepostitory) { } protected override bool DoesPasswordMatch(string password, Client client) { return client.DoesBarServicePasswordMatch(password); } } </code>
public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
    public BarApiAuthenticationProvider(IClientRepository clientRepository)
        : base(clientRepostitory)
    {
    }   

    protected override bool DoesPasswordMatch(string password, Client client)
    {
        return client.DoesBarServicePasswordMatch(password);
    }   
}

Both unit test fixtures for each specific class pass, and normally that would be the end of the thought, however, I have to extend the functionality that’s common to both authentication providers (so I would obviously implement in the template base class).

My questions are:

  1. Should I refactor the test code to remove test duplication? To test the abstract class somehow, and write specific tests for the DoesPasswordMatch method

  2. Should I add new tests to both fixtures to ensure the actual types both work properly? Meaning the tests should have no idea the underlying class is a template, that way if both providers become significantly different where the template no longer makes sense, it could be refactored away without losing code coverage.

  3. Should I leave both test fixtures the same, and just write a new fixture with tests against the template for the new functionality?

  4. Is there something else I am not considering?

Remove the duplicate test code

Having tests on your child-classes for code that is in the parent class means twice the maintenance if the parent class changes. The reason you move to a parent class is specifically to avoid duplicating code, actual code as well as tests. (And if you ever end up creating a third child you might end up thinking that all tests on the parent class need to be duplicated AGAIN. So you can see where that leads you)

Test the parent class logic on the parent class, not through the children

Depending on the language you can use mocks, or write a specific implementation of the parent class that allows you to test the parent class methods (a TestableApiAuthenticationProviderTemplate). This would also test that when the preconditions for GetPrincipal are valid the DoesPasswordMatch-method is called.

Test your child classes independently

Each child class can have its own test class that only tests the code in the child. Note that you might have to change the signature for the DoesPasswordMatch to become public in order to facilitate testing. You could go through the parent method and test by calling GetPrincipal but you could end up doing a lot of setting up code so that it can reach the actual code you are testing. And again, when changing the code of the parent (because you want to check an additional parameter) you would have to change all the tests of the child classes.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa

Unit testing a template class after refactoring

I am cleaning up my code by way of removing duplicates, and found two classes that were almost identical, out of 55 lines, only a single predicate in an if statement differed between them.

Both classes also had a suite of tests, which were almost identical copies as well.

The two original classes were FooApiAuthenticationProvider and BarApiAuthenticationProvider.

Below is my current refactored code using a template-pattern refactoring.

Template

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider
{
private readonly IClientRepository _clientRepository;
protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository)
{
if (clientRepository == null)
{
throw new ArgumentNullException("clientRepository");
}
_clientRepository = clientRepository;
}
public GenericPrincipal GetPrincipal(string username, string password)
{
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentNullException("username");
}
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("password");
}
var client = clientRepository.GetByUsername(username);
if (client == null && !DoesPasswordMatch(password, client))
{
return null;
}
return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID);
}
protected abstract bool DoesPasswordMatch(string password, Client client);
}
</code>
<code>public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider { private readonly IClientRepository _clientRepository; protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository) { if (clientRepository == null) { throw new ArgumentNullException("clientRepository"); } _clientRepository = clientRepository; } public GenericPrincipal GetPrincipal(string username, string password) { if (string.IsNullOrWhiteSpace(username)) { throw new ArgumentNullException("username"); } if (string.IsNullOrWhiteSpace(password)) { throw new ArgumentNullException("password"); } var client = clientRepository.GetByUsername(username); if (client == null && !DoesPasswordMatch(password, client)) { return null; } return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID); } protected abstract bool DoesPasswordMatch(string password, Client client); } </code>
public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider
{
    private readonly IClientRepository _clientRepository;

    protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository)
    {
        if (clientRepository == null)
        {
            throw new ArgumentNullException("clientRepository");
        }

        _clientRepository = clientRepository;
    }

    public GenericPrincipal GetPrincipal(string username, string password)
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            throw new ArgumentNullException("username");
        }

        if (string.IsNullOrWhiteSpace(password))
        {
            throw new ArgumentNullException("password");
        }

        var client = clientRepository.GetByUsername(username);

        if (client == null && !DoesPasswordMatch(password, client))
        {
            return null;
        }

        return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID);
    }

    protected abstract bool DoesPasswordMatch(string password, Client client);
}

FooApiAuthenticationProvider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
public FooApiAuthenticationProvider(IClientRepository clientRepository)
: base(clientRepostitory)
{
}
protected override bool DoesPasswordMatch(string password, Client client)
{
return client.FooApiPassword == password;
}
}
</code>
<code>public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate { public FooApiAuthenticationProvider(IClientRepository clientRepository) : base(clientRepostitory) { } protected override bool DoesPasswordMatch(string password, Client client) { return client.FooApiPassword == password; } } </code>
public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
    public FooApiAuthenticationProvider(IClientRepository clientRepository)
        : base(clientRepostitory)
    {
    }

    protected override bool DoesPasswordMatch(string password, Client client)
    {
        return client.FooApiPassword == password;
    }
}

BarApiAuthenticationProvider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
public BarApiAuthenticationProvider(IClientRepository clientRepository)
: base(clientRepostitory)
{
}
protected override bool DoesPasswordMatch(string password, Client client)
{
return client.DoesBarServicePasswordMatch(password);
}
}
</code>
<code>public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate { public BarApiAuthenticationProvider(IClientRepository clientRepository) : base(clientRepostitory) { } protected override bool DoesPasswordMatch(string password, Client client) { return client.DoesBarServicePasswordMatch(password); } } </code>
public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
    public BarApiAuthenticationProvider(IClientRepository clientRepository)
        : base(clientRepostitory)
    {
    }   

    protected override bool DoesPasswordMatch(string password, Client client)
    {
        return client.DoesBarServicePasswordMatch(password);
    }   
}

Both unit test fixtures for each specific class pass, and normally that would be the end of the thought, however, I have to extend the functionality that’s common to both authentication providers (so I would obviously implement in the template base class).

My questions are:

  1. Should I refactor the test code to remove test duplication? To test the abstract class somehow, and write specific tests for the DoesPasswordMatch method

  2. Should I add new tests to both fixtures to ensure the actual types both work properly? Meaning the tests should have no idea the underlying class is a template, that way if both providers become significantly different where the template no longer makes sense, it could be refactored away without losing code coverage.

  3. Should I leave both test fixtures the same, and just write a new fixture with tests against the template for the new functionality?

  4. Is there something else I am not considering?

Remove the duplicate test code

Having tests on your child-classes for code that is in the parent class means twice the maintenance if the parent class changes. The reason you move to a parent class is specifically to avoid duplicating code, actual code as well as tests. (And if you ever end up creating a third child you might end up thinking that all tests on the parent class need to be duplicated AGAIN. So you can see where that leads you)

Test the parent class logic on the parent class, not through the children

Depending on the language you can use mocks, or write a specific implementation of the parent class that allows you to test the parent class methods (a TestableApiAuthenticationProviderTemplate). This would also test that when the preconditions for GetPrincipal are valid the DoesPasswordMatch-method is called.

Test your child classes independently

Each child class can have its own test class that only tests the code in the child. Note that you might have to change the signature for the DoesPasswordMatch to become public in order to facilitate testing. You could go through the parent method and test by calling GetPrincipal but you could end up doing a lot of setting up code so that it can reach the actual code you are testing. And again, when changing the code of the parent (because you want to check an additional parameter) you would have to change all the tests of the child classes.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa

Unit testing a template class after refactoring

I am cleaning up my code by way of removing duplicates, and found two classes that were almost identical, out of 55 lines, only a single predicate in an if statement differed between them.

Both classes also had a suite of tests, which were almost identical copies as well.

The two original classes were FooApiAuthenticationProvider and BarApiAuthenticationProvider.

Below is my current refactored code using a template-pattern refactoring.

Template

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider
{
private readonly IClientRepository _clientRepository;
protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository)
{
if (clientRepository == null)
{
throw new ArgumentNullException("clientRepository");
}
_clientRepository = clientRepository;
}
public GenericPrincipal GetPrincipal(string username, string password)
{
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentNullException("username");
}
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("password");
}
var client = clientRepository.GetByUsername(username);
if (client == null && !DoesPasswordMatch(password, client))
{
return null;
}
return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID);
}
protected abstract bool DoesPasswordMatch(string password, Client client);
}
</code>
<code>public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider { private readonly IClientRepository _clientRepository; protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository) { if (clientRepository == null) { throw new ArgumentNullException("clientRepository"); } _clientRepository = clientRepository; } public GenericPrincipal GetPrincipal(string username, string password) { if (string.IsNullOrWhiteSpace(username)) { throw new ArgumentNullException("username"); } if (string.IsNullOrWhiteSpace(password)) { throw new ArgumentNullException("password"); } var client = clientRepository.GetByUsername(username); if (client == null && !DoesPasswordMatch(password, client)) { return null; } return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID); } protected abstract bool DoesPasswordMatch(string password, Client client); } </code>
public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider
{
    private readonly IClientRepository _clientRepository;

    protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository)
    {
        if (clientRepository == null)
        {
            throw new ArgumentNullException("clientRepository");
        }

        _clientRepository = clientRepository;
    }

    public GenericPrincipal GetPrincipal(string username, string password)
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            throw new ArgumentNullException("username");
        }

        if (string.IsNullOrWhiteSpace(password))
        {
            throw new ArgumentNullException("password");
        }

        var client = clientRepository.GetByUsername(username);

        if (client == null && !DoesPasswordMatch(password, client))
        {
            return null;
        }

        return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID);
    }

    protected abstract bool DoesPasswordMatch(string password, Client client);
}

FooApiAuthenticationProvider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
public FooApiAuthenticationProvider(IClientRepository clientRepository)
: base(clientRepostitory)
{
}
protected override bool DoesPasswordMatch(string password, Client client)
{
return client.FooApiPassword == password;
}
}
</code>
<code>public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate { public FooApiAuthenticationProvider(IClientRepository clientRepository) : base(clientRepostitory) { } protected override bool DoesPasswordMatch(string password, Client client) { return client.FooApiPassword == password; } } </code>
public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
    public FooApiAuthenticationProvider(IClientRepository clientRepository)
        : base(clientRepostitory)
    {
    }

    protected override bool DoesPasswordMatch(string password, Client client)
    {
        return client.FooApiPassword == password;
    }
}

BarApiAuthenticationProvider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
public BarApiAuthenticationProvider(IClientRepository clientRepository)
: base(clientRepostitory)
{
}
protected override bool DoesPasswordMatch(string password, Client client)
{
return client.DoesBarServicePasswordMatch(password);
}
}
</code>
<code>public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate { public BarApiAuthenticationProvider(IClientRepository clientRepository) : base(clientRepostitory) { } protected override bool DoesPasswordMatch(string password, Client client) { return client.DoesBarServicePasswordMatch(password); } } </code>
public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
    public BarApiAuthenticationProvider(IClientRepository clientRepository)
        : base(clientRepostitory)
    {
    }   

    protected override bool DoesPasswordMatch(string password, Client client)
    {
        return client.DoesBarServicePasswordMatch(password);
    }   
}

Both unit test fixtures for each specific class pass, and normally that would be the end of the thought, however, I have to extend the functionality that’s common to both authentication providers (so I would obviously implement in the template base class).

My questions are:

  1. Should I refactor the test code to remove test duplication? To test the abstract class somehow, and write specific tests for the DoesPasswordMatch method

  2. Should I add new tests to both fixtures to ensure the actual types both work properly? Meaning the tests should have no idea the underlying class is a template, that way if both providers become significantly different where the template no longer makes sense, it could be refactored away without losing code coverage.

  3. Should I leave both test fixtures the same, and just write a new fixture with tests against the template for the new functionality?

  4. Is there something else I am not considering?

Remove the duplicate test code

Having tests on your child-classes for code that is in the parent class means twice the maintenance if the parent class changes. The reason you move to a parent class is specifically to avoid duplicating code, actual code as well as tests. (And if you ever end up creating a third child you might end up thinking that all tests on the parent class need to be duplicated AGAIN. So you can see where that leads you)

Test the parent class logic on the parent class, not through the children

Depending on the language you can use mocks, or write a specific implementation of the parent class that allows you to test the parent class methods (a TestableApiAuthenticationProviderTemplate). This would also test that when the preconditions for GetPrincipal are valid the DoesPasswordMatch-method is called.

Test your child classes independently

Each child class can have its own test class that only tests the code in the child. Note that you might have to change the signature for the DoesPasswordMatch to become public in order to facilitate testing. You could go through the parent method and test by calling GetPrincipal but you could end up doing a lot of setting up code so that it can reach the actual code you are testing. And again, when changing the code of the parent (because you want to check an additional parameter) you would have to change all the tests of the child classes.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa

Unit testing a template class after refactoring

I am cleaning up my code by way of removing duplicates, and found two classes that were almost identical, out of 55 lines, only a single predicate in an if statement differed between them.

Both classes also had a suite of tests, which were almost identical copies as well.

The two original classes were FooApiAuthenticationProvider and BarApiAuthenticationProvider.

Below is my current refactored code using a template-pattern refactoring.

Template

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider
{
private readonly IClientRepository _clientRepository;
protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository)
{
if (clientRepository == null)
{
throw new ArgumentNullException("clientRepository");
}
_clientRepository = clientRepository;
}
public GenericPrincipal GetPrincipal(string username, string password)
{
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentNullException("username");
}
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("password");
}
var client = clientRepository.GetByUsername(username);
if (client == null && !DoesPasswordMatch(password, client))
{
return null;
}
return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID);
}
protected abstract bool DoesPasswordMatch(string password, Client client);
}
</code>
<code>public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider { private readonly IClientRepository _clientRepository; protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository) { if (clientRepository == null) { throw new ArgumentNullException("clientRepository"); } _clientRepository = clientRepository; } public GenericPrincipal GetPrincipal(string username, string password) { if (string.IsNullOrWhiteSpace(username)) { throw new ArgumentNullException("username"); } if (string.IsNullOrWhiteSpace(password)) { throw new ArgumentNullException("password"); } var client = clientRepository.GetByUsername(username); if (client == null && !DoesPasswordMatch(password, client)) { return null; } return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID); } protected abstract bool DoesPasswordMatch(string password, Client client); } </code>
public abstract class ApiAuthenticationProviderTemplate : IApiAuthenticationProvider
{
    private readonly IClientRepository _clientRepository;

    protected ApiAuthenticationProviderTemplate(IClientRepository clientRepository)
    {
        if (clientRepository == null)
        {
            throw new ArgumentNullException("clientRepository");
        }

        _clientRepository = clientRepository;
    }

    public GenericPrincipal GetPrincipal(string username, string password)
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            throw new ArgumentNullException("username");
        }

        if (string.IsNullOrWhiteSpace(password))
        {
            throw new ArgumentNullException("password");
        }

        var client = clientRepository.GetByUsername(username);

        if (client == null && !DoesPasswordMatch(password, client))
        {
            return null;
        }

        return PrincipalBuilder.BuildPrinciple(client.LoginIdentifier, client.ID);
    }

    protected abstract bool DoesPasswordMatch(string password, Client client);
}

FooApiAuthenticationProvider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
public FooApiAuthenticationProvider(IClientRepository clientRepository)
: base(clientRepostitory)
{
}
protected override bool DoesPasswordMatch(string password, Client client)
{
return client.FooApiPassword == password;
}
}
</code>
<code>public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate { public FooApiAuthenticationProvider(IClientRepository clientRepository) : base(clientRepostitory) { } protected override bool DoesPasswordMatch(string password, Client client) { return client.FooApiPassword == password; } } </code>
public sealed class FooApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
    public FooApiAuthenticationProvider(IClientRepository clientRepository)
        : base(clientRepostitory)
    {
    }

    protected override bool DoesPasswordMatch(string password, Client client)
    {
        return client.FooApiPassword == password;
    }
}

BarApiAuthenticationProvider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
public BarApiAuthenticationProvider(IClientRepository clientRepository)
: base(clientRepostitory)
{
}
protected override bool DoesPasswordMatch(string password, Client client)
{
return client.DoesBarServicePasswordMatch(password);
}
}
</code>
<code>public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate { public BarApiAuthenticationProvider(IClientRepository clientRepository) : base(clientRepostitory) { } protected override bool DoesPasswordMatch(string password, Client client) { return client.DoesBarServicePasswordMatch(password); } } </code>
public sealed class BarApiAuthenticationProvider : ApiAuthenticationProviderTemplate
{
    public BarApiAuthenticationProvider(IClientRepository clientRepository)
        : base(clientRepostitory)
    {
    }   

    protected override bool DoesPasswordMatch(string password, Client client)
    {
        return client.DoesBarServicePasswordMatch(password);
    }   
}

Both unit test fixtures for each specific class pass, and normally that would be the end of the thought, however, I have to extend the functionality that’s common to both authentication providers (so I would obviously implement in the template base class).

My questions are:

  1. Should I refactor the test code to remove test duplication? To test the abstract class somehow, and write specific tests for the DoesPasswordMatch method

  2. Should I add new tests to both fixtures to ensure the actual types both work properly? Meaning the tests should have no idea the underlying class is a template, that way if both providers become significantly different where the template no longer makes sense, it could be refactored away without losing code coverage.

  3. Should I leave both test fixtures the same, and just write a new fixture with tests against the template for the new functionality?

  4. Is there something else I am not considering?

Remove the duplicate test code

Having tests on your child-classes for code that is in the parent class means twice the maintenance if the parent class changes. The reason you move to a parent class is specifically to avoid duplicating code, actual code as well as tests. (And if you ever end up creating a third child you might end up thinking that all tests on the parent class need to be duplicated AGAIN. So you can see where that leads you)

Test the parent class logic on the parent class, not through the children

Depending on the language you can use mocks, or write a specific implementation of the parent class that allows you to test the parent class methods (a TestableApiAuthenticationProviderTemplate). This would also test that when the preconditions for GetPrincipal are valid the DoesPasswordMatch-method is called.

Test your child classes independently

Each child class can have its own test class that only tests the code in the child. Note that you might have to change the signature for the DoesPasswordMatch to become public in order to facilitate testing. You could go through the parent method and test by calling GetPrincipal but you could end up doing a lot of setting up code so that it can reach the actual code you are testing. And again, when changing the code of the parent (because you want to check an additional parameter) you would have to change all the tests of the child classes.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật