cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
1409
Views
5
Helpful
1
Replies

Mockup for Unit testing of NCS python package

DTtb
Level 1
Level 1

Hi all,

 

is it possible to mockup the arguments (tctx, root, service, proplist) for the service callback without using of NCS daemon?

The approch should be to validate the output based on the input for simple unit testing with pyTest and speed up the development to avoid package reload after every code change.

For example: Inside cb_create does exist a function to calculate broadcast ip address based on passed ip address. I want to validate the calculated ip address.

 

import ncs
from ncs.application import Service

# ------------------------
# SERVICE CALLBACK EXAMPLE
# ------------------------
class ServiceCallbacks(Service):

    # The create() callback is invoked inside NCS FASTMAP and
    # must always exist.
    @Service.create
    def cb_create(self, tctx, root, service, proplist):
        self.log.info('Service create(service=', service._path, ')') 

I am aware of it, that the tests need further stages inside ncs to validate the service.

 

Thanks.

1 Accepted Solution

Accepted Solutions

yfherzog
Cisco Employee
Cisco Employee

I'm not the unit tests expert (sadly), but asked myself the same question a while back, and was able to come up with something.

Probably there are more elegant ways to do that...

 

The tests below are testing a very simple NSO service package. It tests the methods that the create callback is calling and then also tests cb_create() itself to make sure it calls everything it should.

 

Hope it helps!

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import mock
import unittest
import sys
sys.path.insert(0, '../../python')
mock.patch('ncs.application.Service.create', staticmethod(lambda x: x)).start()
from nso_basic.main import ServiceCallbacks  # noqa: E402


@mock.patch.object(ServiceCallbacks, "__init__", lambda x, y, z, w: None)
class nsoBasicTestCase(unittest.TestCase):

    def test_foo(self):
        # instantiate our service
        instance = ServiceCallbacks(None, None, None)
        instance.log = log()

        self.assertEqual(instance.foo(None), None, "Should be None")

    def test_calc_mask(self):
        # instantiate our service
        instance = ServiceCallbacks(None, None, None)
        instance.log = log()

        service = mock.Mock()
        service.ip = '1.2.3.4'
        service.mask = '16'

        self.assertEqual(instance.calc_mask(service),
                         '255.255.0.0',
                         'Should be 255.255.0.0')

    def test_calc_desc(self):
        # instantiate our service
        instance = ServiceCallbacks(None, None, None)
        instance.log = log()

        service = mock.Mock()
        service.description = 'short desc'

        self.assertEqual(instance.calc_desc(service),
                         'short desc',
                         'Should be short desc')

        service.description = 'very very long description'

        self.assertEqual(instance.calc_desc(service),
                         'very-ption',
                         'Should be very-ption')

    # decorators are applied BOTTOM-UP.
    # Order of argument for the test method should match
    @mock.patch('ncs.template.Template', autospec=True)
    @mock.patch('ncs.template.Variables', autospec=True)
    def test_cb_create(self, mock_variable, mock_template):
        '''
        Test that cb_create is calling all functions it suppose to call.
        '''
        # instantiate our service
        instance = ServiceCallbacks(None, None, None)

        instance.log = log()
        instance.calc_mask = mock.MagicMock(return_value='255.255.0.0')
        instance.calc_desc = mock.MagicMock(return_value='short desc')

        service = mock.Mock()
        service._path = 'cb/create/test'
        service.description = 'short desc'
        service.ip = '1.2.3.4'
        service.mask = '16'

        # Mock a Variables instance to be able to count the times add is called
        instance.mock_var = mock_variable()

        # Same for Template and apply method
        instance.mock_temp = mock_template(service)

        tctx = None
        root = None
        proplist = None
        instance.cb_create(tctx, root, service, proplist)
        self.assertTrue(instance.calc_mask.called)
        self.assertTrue(instance.calc_desc.called)
        self.assertEqual(instance.mock_var.add.call_count, 2)
        self.assertEqual(instance.mock_temp.apply.call_count, 1)


class log():
    def debug(self, *args):
        print('DEBUG: {}'.format(''.join(str(x) for x in args)))

    def info(self, *args):
        print('INFO: {}'.format(''.join(str(x) for x in args)))

    def error(self, *args):
        print('ERROR: {}'.format(''.join(str(x) for x in args)))

View solution in original post

1 Reply 1

yfherzog
Cisco Employee
Cisco Employee

I'm not the unit tests expert (sadly), but asked myself the same question a while back, and was able to come up with something.

Probably there are more elegant ways to do that...

 

The tests below are testing a very simple NSO service package. It tests the methods that the create callback is calling and then also tests cb_create() itself to make sure it calls everything it should.

 

Hope it helps!

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import mock
import unittest
import sys
sys.path.insert(0, '../../python')
mock.patch('ncs.application.Service.create', staticmethod(lambda x: x)).start()
from nso_basic.main import ServiceCallbacks  # noqa: E402


@mock.patch.object(ServiceCallbacks, "__init__", lambda x, y, z, w: None)
class nsoBasicTestCase(unittest.TestCase):

    def test_foo(self):
        # instantiate our service
        instance = ServiceCallbacks(None, None, None)
        instance.log = log()

        self.assertEqual(instance.foo(None), None, "Should be None")

    def test_calc_mask(self):
        # instantiate our service
        instance = ServiceCallbacks(None, None, None)
        instance.log = log()

        service = mock.Mock()
        service.ip = '1.2.3.4'
        service.mask = '16'

        self.assertEqual(instance.calc_mask(service),
                         '255.255.0.0',
                         'Should be 255.255.0.0')

    def test_calc_desc(self):
        # instantiate our service
        instance = ServiceCallbacks(None, None, None)
        instance.log = log()

        service = mock.Mock()
        service.description = 'short desc'

        self.assertEqual(instance.calc_desc(service),
                         'short desc',
                         'Should be short desc')

        service.description = 'very very long description'

        self.assertEqual(instance.calc_desc(service),
                         'very-ption',
                         'Should be very-ption')

    # decorators are applied BOTTOM-UP.
    # Order of argument for the test method should match
    @mock.patch('ncs.template.Template', autospec=True)
    @mock.patch('ncs.template.Variables', autospec=True)
    def test_cb_create(self, mock_variable, mock_template):
        '''
        Test that cb_create is calling all functions it suppose to call.
        '''
        # instantiate our service
        instance = ServiceCallbacks(None, None, None)

        instance.log = log()
        instance.calc_mask = mock.MagicMock(return_value='255.255.0.0')
        instance.calc_desc = mock.MagicMock(return_value='short desc')

        service = mock.Mock()
        service._path = 'cb/create/test'
        service.description = 'short desc'
        service.ip = '1.2.3.4'
        service.mask = '16'

        # Mock a Variables instance to be able to count the times add is called
        instance.mock_var = mock_variable()

        # Same for Template and apply method
        instance.mock_temp = mock_template(service)

        tctx = None
        root = None
        proplist = None
        instance.cb_create(tctx, root, service, proplist)
        self.assertTrue(instance.calc_mask.called)
        self.assertTrue(instance.calc_desc.called)
        self.assertEqual(instance.mock_var.add.call_count, 2)
        self.assertEqual(instance.mock_temp.apply.call_count, 1)


class log():
    def debug(self, *args):
        print('DEBUG: {}'.format(''.join(str(x) for x in args)))

    def info(self, *args):
        print('INFO: {}'.format(''.join(str(x) for x in args)))

    def error(self, *args):
        print('ERROR: {}'.format(''.join(str(x) for x in args)))