0

How do I execute an automated test with the option of specifying it as a unit test or "light-weight integration test" without writing the same test twice and only changing the interface it depends on to make it either of the two?

Specifically, I want to execute one test and specify it as a unit test or an integration test. Based on the mode I select, the test should generate a service interface.

  • I do not want to maintain two sets of identical code with the only difference being an interface:
    • Service for accessing external system (integration test)
    • MockService (unit test)

Example: Construct testable business layer logic

Community
  • 1
  • 1
Scott Nimrod
  • 11,206
  • 11
  • 54
  • 118
  • 1
    You could have two tests that call some common code. Each individual test would setup the dependencies (either mock or otherwise). – Matthew Jan 07 '15 at 15:03
  • I recommend having separate projects for unit and integration tests. Combining the two makes it more difficult to run just the unit tests in the IDE, adds some complexity to the CI builds, and makes it easy to mistakenly flag an integration test as a unit test. – TrueWill Jan 07 '15 at 15:25
  • @David: I am targeting the business logic for my “unit tests” (for speed) and the external dependencies (database) for my integration tests. I do not see much business value in unit testing classes that are going to duplicate testing via their interfaces. If I do perform unit tests as you describe, then I risk having to maintain a larger number of tests that overlap the interface tests. However, I do see value in testing the view-model as the primary interface. I will consider extracting out the test logic and just passing in the dependencies as Matthew advised. – Scott Nimrod Jan 07 '15 at 15:28

4 Answers4

2

It doesn't make sense to have a morphic test.

A unit test tests that a single piece of code works in isolation.

An integration tests tests that your code works when integrated into a larger codebase.

For instance, acceptance criteria and psuedocode for unit testing a viewmodel:

public TestMeViewModelTests {
    public when_adding_a_warehouse_then_should_call_service_AddNewWarehouse_given_WarehouseModel {
        //Arrange
        var warehouseViewModel = new WarehouseViewModel { id=1 };

        var service = new Mock<IService>();

        var interfaceViewModel = new TestMeViewModel(service.Object);

        //Act
        interfaceViewModel.AddWarehouseCommand(warehouseViewModel);

        //Assert
        service.Verify(s=>s.AddNewWarehouse(wareHouseViewModel), Times.Once);
    }
}

See, there's no cross-pollination of concerns. You're just testing that an idempotent operation is called when adding a new warehouse. If you were using an ORM, then you'd also have unit tests verifying that the dataservice calls are occurring.

If you were going to do an integration test, then your test project would be pointed to a "WarehouseTest" connectionstring that mirrors production, and your integration test might do that same logic, but then check to make sure that the warehouse that is inserted by the test is actually in your DB at the end of the test.

C Bauer
  • 5,003
  • 4
  • 33
  • 62
  • See my comment on the initial question. Again, I am targeting speed for unit tests. – Scott Nimrod Jan 07 '15 at 15:30
  • When you mock an interface, you're not testing the underlying type. You're just testing that, under ideal circumstances and everything is working perfectly, that your code does what you expect it to. – C Bauer Jan 07 '15 at 15:44
  • So based on your answer, I shouldn't unit test a view-model? – Scott Nimrod Jan 07 '15 at 15:46
  • My view-model calls a service. That service hits the model which in-turn hits the data access layer. As a result, my viewmodel delegates the task of business logic to the service interface which could then either hit the model and database or serve as a mock and only hit the model with mock data. – Scott Nimrod Jan 07 '15 at 15:53
  • It is perfectly acceptable to have a view-model call a service when a command is invoked on it. Having a viewmodel call a service is NOT business logic. The service sends a request to the model and the business logic gets executed within the model itself. Why do you assume that a view-model cannot be unit tested? – Scott Nimrod Jan 07 '15 at 16:09
  • My service is based on an interface (i.e. actualService or mockService). My view-model relies on the dependency-injected service to handle user-requests. As a result, the same business logic gets executed with the only difference being how the data is retrived which is dictated by the service. – Scott Nimrod Jan 07 '15 at 16:22
  • Can you edit into the OP an example of your integration and unit tests, so that we can have a less theoretical conversation? I'm also going to delete some of my previous comments, to unclutter this space. – C Bauer Jan 07 '15 at 16:26
  • http://stackoverflow.com/questions/26871519/construct-testable-business-layer-logic/26871728#26871728 – Scott Nimrod Jan 07 '15 at 16:38
  • Your words were: "An integration tests tests that your code works when integrated into a larger codebase." That is exactly what I'm testing. I do agree that more value could be generated within an integration test by also verifying the data is added or updated in the database. However, at this point I am targeting model behavior with a database dependency versus CRUD testing. Thanks. – Scott Nimrod Jan 07 '15 at 17:11
  • Your question is "how do I not write the same test twice". The answer is depending on where the code is, you might write the same test twice, once in your unit tests to test the case in isolation, and once in integration tests as part of a larger system test case. – C Bauer Jan 07 '15 at 19:19
  • Wait wait wait, I think I hugely misunderstood something here. Are you looking for an abstract factory? – C Bauer Jan 07 '15 at 19:26
  • I posted the solution. – Scott Nimrod Jan 08 '15 at 13:06
0

Okay, I think I understand what's happening now.

You want to be able to change the implementation used of an interface at runtime in order to change the location the unit tests run against.

In that case, you want some kind of abstract factory pattern.

Example:

public class ViewModel {
    IService _service;
    public ViewModel(IServiceFactory factory){
        _service = factory.Create();
    }
    public void SaveWarehouse(Warehouse aWarehouse) {
        _service.AddWarehouse(aWarehouse);
    }
}
public interface IServiceFactory {
    IService Create();
}
public class ProductionFactory : IServiceFactory { //Dependency injected
    public IService Create() {
        return new ProdService();
    }
}

Unit Test:

public class ViewModelTest {
   public void when_adding_warehouse() {
       //Arrange
        var aWarehouse = new WarehouseViewModel { id=1 };

        var serviceFactory = new Mock<IServiceFactory>().Object);
        var service = new Mock<IService>();
        serviceFactory.Setup(factory => factory.Create()).Returns(service.Object);

        var viewModel = new ViewModel(serviceFactory.Object);

        //Act
        viewModel.AddWarehouseCommand(warehouseViewModel);

        //Assert
        service.Verify(s=>s.AddNewWarehouse(aWarehouse), Times.Once);
   }
}

Integration Test:

Your integration test will include a local internal IService implementation and a local internal IServiceFactory implementation returning the local IService implementation. All of your tests will run perfectly fine and you can control where the data goes to very easily.

C Bauer
  • 5,003
  • 4
  • 33
  • 62
0

Add an entry to the app config.

App Config:

  <appSettings>
    <add key="IsUnitTest" value="True" />
  </appSettings>

Then get the key/value pair from the config file and set your service dependency based on the config value.

Test

[TestClass]
public class MyTest
{
    IServices _service = null;

    [TestInitialize]
    public void Setup()
    {
        var isUnitTest = bool.Parse(ConfigurationManager.AppSettings["IsUnitTest"]);

        if (isUnitTest)
        {
            _service = new MockService();
        }
        else
        {
            _service = new Service();
        }
    }

. . .

Scott Nimrod
  • 11,206
  • 11
  • 54
  • 118
  • For anyone else that comes across this solution: http://artofunittesting.com/unit-testing-review-guidelines/ This solution violates just about every rule for testing established by every person in the field of computer science. Examples from the link: 1. Make sure the test does not contain logic or dynamic values (configuration manager bool frobnicating); 2. Make sure unit tests are separated from integration tests (this is meant for flipping a switch for unit/integration tests); 3. Make sure tests are isolated from each other and repeatable (this sets up some kind of field used for testing) – C Bauer Jan 08 '15 at 13:13
  • As a developer and an SDET, this method resolves my problem. I now can execute the same logic and at the flip of a switch, can have that logic hit the actual database or just have it hit a Mock database. Remember, this is a method that relieves me from having to duplicate tests and maintain an even larger code base. Now, I can then write a script to execute this in both unit test mode and integration mode. – Scott Nimrod Jan 08 '15 at 13:22
  • That's fine. But the people who developed TDD from the ground up and write books about best practices disagree with with everything you're doing and why you're doing it. I mean, you're literally violating 3 best practices every time you do this logic. It's okay for you, but I hope no one gets any ideas that this is at all a wise decision. – C Bauer Jan 08 '15 at 13:47
  • I understand your concern. You have to admit though that I am getting the best "bang for the buck" with this implementation. Yes you're absolutely right: DETAILED integration tests should be separated and should thoroughly examine external system state and behavior. That can be scheduled later. Thank you again for your insight. – Scott Nimrod Jan 08 '15 at 13:54
  • 1
    Believe me, I'd prefer to do it your way too! I look at stack overflow as a knowledge base for future users who might have similar issues, and I know that BP's are developed because issues were found with the "simple implementation". I'm not trying to harp on you, but provide the view that is generated by consensus. – C Bauer Jan 08 '15 at 13:58
  • Then consider this a TDD tool for a developer and not QA. – Scott Nimrod Jan 08 '15 at 14:17
  • This is not a "tool", it's just plain bad tests. – C Bauer Jan 08 '15 at 14:30
  • One could argue that this could support agile behavior in regards to just enough automated tests for development purposes and NOT QA. Again, you are failing to observe the business value of my decision during this stage of development. How is it a bad test if it can pass when using mock data but fail when hitting the database via a db exception using the same test but toggling test modes. I get light-weight integration tests for free with the code I already written for the unit tests. – Scott Nimrod Jan 08 '15 at 14:56
  • Laziness is not a good excuse for writing bad unit tests. Everything that you've said so far hinges on this mentality of "well I don't have to write an additional integration test!". *But that's exactly what you are supposed to do*. You are **supposed** to separate unit tests and integration tests. Your unit tests are **supposed** to not contain logic **at all**. Configuration is supposed to be abstracted to another layer so you can test your code in isolation. You've made 1 valid test into 2 invalid tests, all in the name of laziness. – C Bauer Jan 08 '15 at 15:42
  • As a developer my job is not to write integration tests if I have a QA department that fulfills that function. The boss pays me to write a minimal amount of code to satisfy a feature for the software. Unit tests are a side-effect of the code I produce. Remember, the boss is paying a separate team to test that software. – Scott Nimrod Jan 08 '15 at 17:19
  • So why are you worrying about integration tests in the first place? Write valid unit tests and stop perjuring your unit tests to provide half-baked integration tests. Like you said, you're not being paid for that. – C Bauer Jan 08 '15 at 17:24
  • Because I want to kick off the light-weight integration tests that are now an engineered side-effect of my unit tests when ever I want the test to run overnight. By the way, see @Rick Anderson's answer regarding your feedback. – Scott Nimrod Jan 08 '15 at 18:18
  • Well my original explanation advising you against doing this has 2 upvotes from other users of the site who presumably have been contributing for more then 1 hour, which Rick apparently just signed up for. I posted a link a the beginning of this chain out to a book written on unit tests that explains in detail the intent of unit and , and in your defense a new user with no reputation and no credentials put a comment in an answer. So, take that as you will. – C Bauer Jan 08 '15 at 19:18
  • You are not being practical right now in regards to the intent of what I want to accomplish. Again, you raise valid arguments for general automated test practices. However, just because something is not written in a book does not mean it's a foolish idea. You're right. I am a lazy developer. I do prefer to work smarter and not harder. I do like the idea of write once and have the ability to test a subject under multiple conditions using the same test code. – Scott Nimrod Jan 08 '15 at 19:36
  • About 12 comments ago, I said "It's okay for you, but I hope no one gets any ideas that this is at all a wise decision". I again advise anyone else who is trying to do this right to not do it this way, and I again say that it's okay for you. I'll again say that stack contains information for future users and that I hope to reduce the impact of the sentiment that laziness makes this okay. Again I'll say that my position in this discussion is driven off of the *consensus of your peers*, peers who **literally** "wrote the book" on this. – C Bauer Jan 08 '15 at 20:33
0

I disagree C Bauer. No consensus here at all. Mocks and dependency injection go a long way to solving this problem. I've seen this approach used more frequently over the last couple of years and it works fine.

Usually in Agile environments where roles are cross functional. Some teams want a single code base/solution to work from. Especially where the size of the code base is relatively small, having "unit" tests able to function as light weight Integration tests works fine. There is no black and white solution here, only the one that works best for the problem at hand. Regardless of what others say there are multiple ways to approach this problem and the solutions/approaches are growing and changing all the time.