Sign in to follow this  
mrpeed

How do I test code that only calls other functions or that only draws to the screen

Recommended Posts

Currently, I'm working on recreating connect four for a portfolio of simple games. I'm also writing unit tests for all the code I can. Some of my code functions though only call other functions, or only draw to the screen...

#EXAMPLES...

def draw_refresh_button_text(self):
	font = pygame.font.Font(None, 24)
	text = font.render("Refresh", True, self.black)
	self.displaysurf.blit(
		text, 
		(
			self.refresh_button_rect.centerx-self.refresh_button_rect.centerx/self.refresh_button_rect.y,
			self.refresh_button_rect.centery-self.refresh_button_rect.centery/self.refresh_button_rect.x
		)
	)
		
      
def draw_disks(self):
	for column in self.grid:
		for disk in column:
			pygame.draw.ellipse(self.displaysurf, disk['color'], disk['rect'])
            
#ONLY CALLS OTHER FUNCTIONS...
def get_input(self): 
	inputmanager.InputManager.get_events()
	inputmanager.InputManager.check_for_quit_event()
	inputmanager.InputManager.update_keyboard_key_state()
	inputmanager.InputManager.get_keyboard_input()
	inputmanager.InputManager.update_mouse_button_state()
	inputmanager.InputManager.get_mouse_input()

How would I go about writing tests for code like this?

Should code like this even be tested?

Should all code be tested?

I'm using the Python unittest module to test. The code here uses Pygame.

Share this post


Link to post
Share on other sites

Personally, I would not test methods like this via unit tests ... because it is not a "contract" you are going to be committing to memory and counting on as you work through other areas of code.

But there is a strategy for testing this kind of thing that is very easy ... and it is good for other cases that do define contracts that matter more.

You would create a "mock"/test object of the input manager, and your mock version would do something like verify that each method was called once during the test, or even that they were called in order ... the methods wouldn't DO anything real in the mock object, they'd just track the calls.

Share this post


Link to post
Share on other sites

In Python is very easy to monkey-patch mock functions and objects so you can test without hitting actual system, network, database, or screen draw calls.  Mocking a method of a class by binding a lambda to a side_effect attribute of the MagicMock lets you get away with injecting captive test data into your system from the bottom and then verifying the results returned by your higher-level functions.

Also, thinking about how you're going to mock required dependencies helps you architect your code better.

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Announcements

  • Forum Statistics

    • Total Topics
      628383
    • Total Posts
      2982379
  • Similar Content

    • By Bob Dylan
      I am doing a little physics project with circle circle collisions for now, and have tried to do impulse resolution for collisions with 2 circles, using the following code. 
      relativeVelocity = (other.doVerletVelocity()).subtract(self.doVerletVelocity()) normDirecVel = relativeVelocity.dotProduct(collisionNormal) restitution = -1 - min(self.restitution, other.restitution) numerator = normDirecVel * restitution impulseScalar = numerator / float(1 / self.mass) + float(1 / other.mass) selfVel = self.doVerletVelocity() otherVel = other.doVerletVelocity() impulse = collisionNormal.scalarMult(impulseScalar) selfDV = impulse.scalarMult(1 / self.mass) otherDV = impulse.scalarMult(1 / other.mass) newSelfVel = selfVel.subtract(selfDV) newOtherVel = otherVel.add(otherDV) self.oldPos = (self.center).subtract(newSelfVel.scalarMult(dt)) other.oldPos = (other.center).subtract(newOtherVel.scalarMult(dt)) The problem seems to be that whatever value I give to self.mass and other.mass, the output stays exactly the same, the values that I used are:
      center = Vector(0, 0) radius = 1 oldPos = Vector(0, 0) accel = Vector(0, 0) mass = 100 restitution = 0.001 center2 = Vector(0, 3.20) radius2 = 1 oldPos2 = Vector(0, 3.201) accel2 = Vector(0, -1) mass2 = 1 restitution2 = 1 the output was:
      0.0      0.0      0.0      2.165000000000114
      0.0      0.0      0.0      2.1360000000001174
      0.0      0.0      0.0      2.1066000000001206
      0.0      0.0      0.0      2.076800000000124
      0.0      0.0      0.0      2.046600000000127
      0.0      0.0      0.0      2.0160000000001306
      0.0      0.0      0.0      1.985000000000134
      CIRCLE INTERSECTION
      0.0      -1.985000000000134      0.0      3.938600000000271
      0.0      -3.970000000000268      0.0      5.891800000000408
      0.0      -5.9550000000004015      0.0      7.844600000000544
      0.0      -7.940000000000535      0.0      9.797000000000681
      I changed the values for the masses to make them higher, bu the output still remained the same, if you could get to the bottom of this, it would be much appreciated.
    • By Bob Dylan
      I have a circle class which has the following attributes: center, radius, old position, acceleration, mass, and restitution.
      I then apply impulse resolution as per this link: https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331.
      Here is the code, implementing that, along with my velocity verlet implementation (this is necessary as it explains why I change the values of the old positions of the circles near the end of the impulseScalar method):
      def doVerletPosition(self): diffPos = (self.center).subtract(self.oldPos) aggregatePos = diffPos.add(self.center) ATT = (self.accel).scalarMult(dt**2) e = ATT.add(aggregatePos) return e def doVerletVelocity(self): deltaD = ((self.center).subtract(self.oldPos)) return deltaD.scalarMult(1/dt) def impulseScalar(self,other): isCollision = self.collisionDetection(other) collisionNormal = isCollision[0] if(isCollision[1] == True): relativeVelocity = (other.doVerletVelocity()).subtract(self.doVerletVelocity()) normDirecVel = relativeVelocity.dotProduct(collisionNormal) restitution = -1-(min(self.restitution,other.restitution)) numerator = restitution * normDirecVel impulseScalar = numerator/(self.invMass + other.invMass) impulse = collisionNormal.scalarMult(impulseScalar) selfVel = (self.doVerletVelocity()) otherVel = other.doVerletVelocity() selfVelDiff = impulse.scalarMult(self.invMass) otherVelDiff = impulse.scalarMult(other.invMass) selfVel = selfVel.subtract(selfVelDiff) otherVel = otherVel.subtract(otherVelDiff) self.oldPos = (self.center).subtract(selfVel) other.oldPos = (other.center).subtract(otherVel) It would help if you accepted the vector methods as correct on face value, and I think that they are named well enough to allow you to figure out what they do, however I can paste them in aswell.
      My main problem is that when I run this, it registers that a collision has happened, yet the values position of the second circle do not change. How would I go about fixing this, as it seems that I am implementing the calculations correctly.
      The values of the first and second circle is:
      center = Vector(0,0) radius = 3 oldPos = Vector(0,0) accel = Vector(0,0) mass = 1 restitution = 0.5 center2 = Vector(0,4.2) radius2 = 1 oldPos2 = Vector(0,4.21) accel2 = Vector(0,-1) mass2 = 1 restitution2 = 0.7 What it returns is here: (it returns the position of the centers)
      0.0 0.0 0.0 4.1896 0.0 0.0 0.0 4.178800000000001 0.0 0.0 0.0 4.167600000000001 0.0 0.0 0.0 4.1560000000000015 0.0 0.0 0.0 4.144000000000002 0.0 0.0 0.0 4.131600000000002 0.0 0.0 0.0 4.118800000000003 0.0 0.0 0.0 4.1056000000000035 0.0 0.0 0.0 4.092000000000004 0.0 0.0 0.0 4.078000000000005 0.0 0.0 0.0 4.063600000000005 0.0 0.0 0.0 4.048800000000006 0.0 0.0 0.0 4.033600000000007 0.0 0.0 0.0 4.018000000000008 0.0 0.0 0.0 4.002000000000009 0.0 0.0 0.0 3.9856000000000096 INTERSECTION 0.0 0.0 0.0 3.9688000000000105 INTERSECTION 0.0 0.0 0.0 3.9516000000000115 INTERSECTION 0.0 0.0 0.0 3.9340000000000126 So when it prints INTERSECTION, surely, the stationary circle must change position, if the impulseScalar method is correct, (as it seems to be (as it follows what is said on that link).
      Even if I let it run for longer, the stationary circle still does not move.
    • By HackerMaster05
      Hello I am a beginner in programmin. I just begun with Python.
      I write a code, but python say "syntax invalid". I use python 3.6.3 and my system is mac.
      My code: print("Hello, this is a calculator. ")
      print("Here you can calculate any numbers you need.")
      # Here I describe for the user, what the program can do.
      num_1 = input("Write the first number: ")
      # Here user write the first number for calculating.
      num_2 = input("Write the second number: ")
      # Here user write the second number for calculating.
      operator = input("Write the matematic operator you need: ")
      # Here the user write the matematic opertator they neee.
      if operator == '+':
          print(int(num_1) + (int(num_2))
      elif operator == '/':
          print(int(num_1) / (int(num_2))
      elif operator == '-':
          print(int(num_1) - (int(num_2))
      elif operator == '*':
          print(int(num_1) * (int(num_2))
      elif operator == '%':
          print(float(num_1) % (float(num_2))
      elif operator == '**':
          print(int(num_1) ** (int(num_2))
      print("Thank you dear user! ")
       
      Thank'!!!
      calculator.py
    • By mrpeed
      I wrote Snake in Python 3 using Pygame and was wondering if anyone can do a code review of it? If this is the appropriate fourm to post such a thing?
      Some things to mention:
      1. I realize I could have used a dict in the place of my Segment class, but I decided to go with the class because it looked more clean to me.
      2. I used recursion heavily, though I could have used a list instead. I decided to do it recursively for practice and fun (I don't use recursion often). 
      3. I don't have doc strings for any of my functions.
      4. I probably could have used my get_all_snake_segment_locations function to avoid recursion.
      5. I set fps to 10 to limit the speed of the game. Is this a bad way to do such a thing?
      6. I attached an input manager I created and unit tests for my game for completeness. Though, I'm only asking the actual game to be reviewed, if you want to look at those you can. Also, note the unit tests are not complete yet for several functions I changed.
      7. I really appreciate anyone who takes the time to give me feedback of any kind. This fourm has been a huge help to me and I'm grateful for everyone's  insight!
      import sys import random import itertools import pygame import inputmanager class Segment: def __init__(self, rect, direction=None, parent=None, child=None): self.rect = rect self.direction = direction self.parent = parent self.child = child class Game: def __init__(self): pygame.init() self.fps_clock = pygame.time.Clock() self.fps = 10 self.window_size = (640, 480) self.displaysurf = pygame.display.set_mode(self.window_size) pygame.display.set_caption("Snake") self.cell_size = (32, 32) self.start_location = (320, 224) self.head_segment = Segment(pygame.Rect(self.start_location, self.cell_size)) self.up = "up" self.down = "down" self.left = "left" self.right = "right" self.black = (0, 0, 0) self.green = (0, 255, 0) self.red = (255, 0, 0) self.direction = None self.extender = None self.cell_locations = set( itertools.product( range(0, self.window_size[0], self.cell_size[0]), range(0, self.window_size[1], self.cell_size[1]) ) ) def main(self): while True: self.get_input() self.update() self.render() self.fps_clock.tick(self.fps) def get_input(self): inputmanager.InputManager.get_events() inputmanager.InputManager.check_for_quit_event() inputmanager.InputManager.update_keyboard_key_state() inputmanager.InputManager.get_keyboard_input() def update(self): self.handle_input() self.update_snake_direction(self.head_segment, self.direction) self.move_snake(self.head_segment) if self.extender is None: self.add_extender_to_board() if self.head_segment_collided_with_extender(): self.extend_snake() if self.game_over(): self.refresh() def handle_input(self): if inputmanager.InputManager.quit: self.terminate() if (inputmanager.InputManager.keyboard[pygame.K_UP] == inputmanager.InputManager.pressed and self.direction != self.down): self.direction = self.up elif (inputmanager.InputManager.keyboard[pygame.K_DOWN] == inputmanager.InputManager.pressed and self.direction != self.up): self.direction = self.down elif (inputmanager.InputManager.keyboard[pygame.K_LEFT] == inputmanager.InputManager.pressed and self.direction != self.right): self.direction = self.left elif (inputmanager.InputManager.keyboard[pygame.K_RIGHT] == inputmanager.InputManager.pressed and self.direction != self.left): self.direction = self.right def terminate(self): pygame.quit() sys.exit() def update_snake_direction(self, segment, parent_direction): ###TEST if segment.child is not None: self.update_snake_direction(segment.child, parent_direction) if segment.parent is None: segment.direction = parent_direction else: segment.direction = segment.parent.direction def move_snake(self, segment): self.move_segment(segment) if segment.child is not None: self.move_snake(segment.child) def move_segment(self, segment): if segment.direction == self.up: segment.rect.move_ip(0, -self.cell_size[1]) elif segment.direction == self.down: segment.rect.move_ip(0, self.cell_size[1]) elif segment.direction == self.left: segment.rect.move_ip(-self.cell_size[0], 0) elif segment.direction == self.right: segment.rect.move_ip(self.cell_size[0], 0) def add_extender_to_board(self): snake_segments_locations = set(self.get_all_snake_segment_locations(self.head_segment)) location = random.choice(list(self.cell_locations-snake_segments_locations)) self.extender = pygame.Rect(location, self.cell_size) def get_all_snake_segment_locations(self, segment): yield segment.rect.topleft if segment.child is not None: yield from self.get_all_snake_segment_locations(segment.child) def head_segment_collided_with_extender(self): return self.head_segment.rect.colliderect(self.extender) def extend_snake(self): self.extender = None self.add_segment_to_snake(self.head_segment) def add_segment_to_snake(self, segment): if segment.child is None: if segment.direction == self.up: topleft = (segment.rect.x, segment.rect.y+self.cell_size[1]) elif segment.direction == self.down: topleft = (segment.rect.x, segment.rect.y-self.cell_size[1]) elif segment.direction == self.left: topleft = (segment.rect.x+self.cell_size[0], segment.rect.y) elif segment.direction == self.right: topleft = (segment.rect.x-self.cell_size[0], segment.rect.y) segment.child = Segment( pygame.Rect(topleft, self.cell_size), segment.direction, segment ) else: self.add_segment_to_snake(segment.child) def game_over(self): return any([ self.head_segment_collided_with_self(self.head_segment), self.head_segment_out_of_bounds() ]) def head_segment_collided_with_self(self, segment): if segment.child is not None: if self.head_segment.rect.colliderect(segment.child.rect): return True else: return self.head_segment_collided_with_self(segment.child) return False def head_segment_out_of_bounds(self): if (self.head_segment.rect.x < 0 or self.head_segment.rect.y < 0 or self.head_segment.rect.x >= self.window_size[0] or self.head_segment.rect.y >= self.window_size[1]): return True return False def refresh(self): self.head_segment = Segment(pygame.Rect(self.start_location, self.cell_size)) self.direction = None self.extender = None def render(self): self.displaysurf.fill(self.black) self.draw_snake(self.head_segment) if self.extender is not None: self.draw_extender() pygame.display.update() def draw_snake(self, segment): pygame.draw.rect(self.displaysurf, self.green, segment.rect) if segment.child is not None: self.draw_snake(segment.child) def draw_extender(self): pygame.draw.rect(self.displaysurf, self.red, self.extender) if __name__ == "__main__": game = Game() game.main()  
      test_game.py
      inputmanager.py
       
      game.py
    • By Bob Dylan
      I know how to calculate the scalar of the velocity vector after a collision with 2 circles (as per this link: https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331)
      These circles cannot rotate and do not have friction but can have different masses, however I cannot seem to find out any way to find the unit vector that I need to multiply the scalar of velocity by to get the new velocity of the particles after the collision.
      I also know how to check if 2 circles are colliding.
      Also, I am only dealing with this in a purely "maths-sense" (ie. the circles have a center and a radius), and would like to know how I can represent these circles on the screen in python 3.0.
      The vector class:
      class Vector(): def __init__(self,x,y): self.x = x self.y = y def add(self, newVector): return Vector(self.x+newVector.x, self.y+newVector.y) def subtract(self,newVector): return Vector(self.x-newVector.x, self.y-newVector.y) def equals(self, newVector): return Vector(newVector.x,newVector.y) def scalarMult(self, scalar): return Vector(self.x*scalar, self.y*scalar) def dotProduct(self, newVector): return (self.x*newVector.x)+(self.y*newVector.y def distance(self): return math.sqrt((self.x)**2 +(self.y)**2) The circle class:
      class Particles(): def __init__(self,currentPos, oldPos, accel, dt,mass, center, radius): self.currentPos = currentPos self.oldPos = oldPos self.accel = accel self.dt = dt self.mass = mass self.center = center self.radius = radius def doVerletPosition(currentPos, oldPos, accel, dt): a = currentPos.subtract(oldPos) b = currentPos.add(a) c = accel.scalarMult(dt) d = c.scalarMult(dt) return d.add(b) def doVerletVelocity(currentPos, oldPos, dt): deltaD = (currentPos.subtract(oldPos)) return deltaD.scalarMult(1/dt) def collisionDetection(self, center, radius): xCenter = (self.radius).xComponent() yCenter = (self.radius).yComponent() xOther = radius.xComponent() yOther = radius.yComponent() if ((xCenter - xOther)**2 + (yCenter-yOther)**2 < (self.radius + radius)**2): return True else: return False I do know about AABBs, but I am only using around 10 particles for now, and AABBs are not necessary now.
  • Popular Now